Autoformat files with prettier
[MAILPOET-4075]
This commit is contained in:
@ -116,7 +116,7 @@ jobs:
|
||||
- checkout:
|
||||
path: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Compute checksum for prefixer"
|
||||
name: 'Compute checksum for prefixer'
|
||||
command: find prefixer -type f -not -path 'prefixer/build/*' -not -path 'prefixer/vendor/*' | sort | xargs cat | sha512sum > prefixer-checksum
|
||||
- restore_cache:
|
||||
key: tools-{{ checksum "tools/install.php" }}
|
||||
@ -133,7 +133,7 @@ jobs:
|
||||
- npm-{{ checksum "package-lock.json" }}
|
||||
- npm- # fallback to most recent npm-* if not found by checksum
|
||||
- run:
|
||||
name: "Set up test environment"
|
||||
name: 'Set up test environment'
|
||||
command: |
|
||||
# install plugin dependencies
|
||||
COMPOSER_DEV_MODE=1 php tools/install.php
|
||||
@ -197,7 +197,7 @@ jobs:
|
||||
at: /home/circleci/mailpoet
|
||||
- add_ssh_keys
|
||||
- run:
|
||||
name: "Install Premium plugin"
|
||||
name: 'Install Premium plugin'
|
||||
command: |
|
||||
# Add GitHub to known_hosts because there is no checkout step in this job
|
||||
echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> ~/.ssh/known_hosts
|
||||
@ -207,7 +207,7 @@ jobs:
|
||||
- restore_cache:
|
||||
key: premium-composer-{{ checksum "mailpoet-premium/composer.json" }}-{{ checksum "mailpoet-premium/composer.lock" }}
|
||||
- run:
|
||||
name: "Set up test environment"
|
||||
name: 'Set up test environment'
|
||||
command: |
|
||||
# install Premium dependencies
|
||||
MAILPOET_FREE_PATH=$(pwd)/mailpoet
|
||||
@ -241,7 +241,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Static analysis"
|
||||
name: 'Static analysis'
|
||||
command: ./do qa:phpstan --php-version=<< parameters.php_version >>
|
||||
qa_js:
|
||||
executor: wpcli_php_latest
|
||||
@ -250,7 +250,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "QA Frontend Assets"
|
||||
name: 'QA Frontend Assets'
|
||||
command: ./do qa:frontend-assets
|
||||
qa_php:
|
||||
executor: wpcli_php_latest
|
||||
@ -259,7 +259,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "QA PHP"
|
||||
name: 'QA PHP'
|
||||
command: ./do qa:php
|
||||
qa_php_oldest:
|
||||
executor: wpcli_php_oldest
|
||||
@ -268,7 +268,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "QA PHP"
|
||||
name: 'QA PHP'
|
||||
command: ./do qa:php
|
||||
qa_php_max_wporg:
|
||||
executor: wpcli_php_max_wporg
|
||||
@ -276,7 +276,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "QA PHP"
|
||||
name: 'QA PHP'
|
||||
command: ./do qa:php-max-wporg
|
||||
js_tests:
|
||||
executor: wpcli_php_latest
|
||||
@ -285,15 +285,15 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Preparing test results folder"
|
||||
name: 'Preparing test results folder'
|
||||
command: mkdir test-results
|
||||
- run:
|
||||
name: "JS Newsletter Editor Tests"
|
||||
name: 'JS Newsletter Editor Tests'
|
||||
command: |
|
||||
mkdir test-results/mocha
|
||||
./do t:newsletter-editor test-results/mocha/newsletter_editor_junit.xml
|
||||
- run:
|
||||
name: "JS Tests"
|
||||
name: 'JS Tests'
|
||||
command: |
|
||||
./do t:j test-results/mocha/junit.xml
|
||||
- store_test_results:
|
||||
@ -343,10 +343,10 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Set up virtual host"
|
||||
name: 'Set up virtual host'
|
||||
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
|
||||
- run:
|
||||
name: "Pull acceptance test docker images"
|
||||
name: 'Pull acceptance test docker images'
|
||||
# Pull docker images with 3 retries
|
||||
command: i='0';while ! docker-compose -f tests/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
|
||||
- when:
|
||||
@ -417,16 +417,16 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Set up virtual host"
|
||||
name: 'Set up virtual host'
|
||||
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
|
||||
- run:
|
||||
name: "Prepare example.com for testing"
|
||||
name: 'Prepare example.com for testing'
|
||||
command: echo 127.0.0.1 example.com | sudo tee -a /etc/hosts
|
||||
- run:
|
||||
name: "Set up test environment"
|
||||
name: 'Set up test environment'
|
||||
command: source ../.circleci/setup.bash && setup php7
|
||||
- run:
|
||||
name: "PHP Unit tests"
|
||||
name: 'PHP Unit tests'
|
||||
command: |
|
||||
./do t:u --xml
|
||||
- store_test_results:
|
||||
@ -453,16 +453,16 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Set up virtual host"
|
||||
name: 'Set up virtual host'
|
||||
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
|
||||
- run:
|
||||
name: "Prepare example.com for testing"
|
||||
name: 'Prepare example.com for testing'
|
||||
command: echo 127.0.0.1 example.com | sudo tee -a /etc/hosts
|
||||
- run:
|
||||
name: "Set up test environment"
|
||||
name: 'Set up test environment'
|
||||
command: << parameters.setup_command >>
|
||||
- run:
|
||||
name: "PHP Integration tests"
|
||||
name: 'PHP Integration tests'
|
||||
command: << parameters.run_command >>
|
||||
- store_test_results:
|
||||
path: tests/_output
|
||||
@ -479,7 +479,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /home/circleci/mailpoet
|
||||
- run:
|
||||
name: "Set up environment"
|
||||
name: 'Set up environment'
|
||||
command: |
|
||||
source ../.circleci/setup.bash && setup php7
|
||||
sudo apt-get update
|
||||
@ -487,7 +487,7 @@ jobs:
|
||||
sed -i 's/^WP_ROOT=.*$/WP_ROOT=\/home\/circleci\/mailpoet\/wordpress/g' .env
|
||||
echo ${CIRCLE_BUILD_NUM} > release_zip_build_number.txt
|
||||
- run:
|
||||
name: "Build"
|
||||
name: 'Build'
|
||||
command: ./build.sh
|
||||
- store_artifacts:
|
||||
path: /home/circleci/mailpoet/mailpoet/mailpoet.zip
|
||||
@ -580,7 +580,7 @@ workflows:
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 22 * * 1-5"
|
||||
cron: '0 22 * * 1-5'
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
3
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@ -4,7 +4,6 @@ about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to ...
|
||||
2. Click on ...
|
||||
3. Scroll down to ...
|
||||
@ -26,6 +26,7 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Versions (please complete the following information):**
|
||||
|
||||
- WordPress version: [e.g: 5.3.2]
|
||||
- PHP version: [e.g: 7.4.2]
|
||||
- MailPoet version: [e.g: 3.46.13]
|
||||
|
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -4,7 +4,6 @@ about: https://feedback.mailpoet.com/
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please use https://feedback.mailpoet.com/ for feature requests.
|
||||
|
22
.github/SECURITY.md
vendored
22
.github/SECURITY.md
vendored
@ -4,7 +4,7 @@ Full details of the Automattic Security Policy can be found on [automattic.com/s
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Generally, *only the latest version of MailPoet has continued support*. If a critical vulnerability is found in the current version of MailPoet, we may opt to backport any patches to previous versions.
|
||||
Generally, _only the latest version of MailPoet has continued support_. If a critical vulnerability is found in the current version of MailPoet, we may opt to backport any patches to previous versions.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
@ -14,9 +14,9 @@ Generally, *only the latest version of MailPoet has continued support*. If a cri
|
||||
|
||||
Our most critical targets are:
|
||||
|
||||
* MailPoet plugin (this repository)
|
||||
* MailPoet Premium
|
||||
* mailpoet.com -- the primary site, and all of it subdomains, e.g. [account.mailpoet.com](https://account.mailpoet.com/)
|
||||
- MailPoet plugin (this repository)
|
||||
- MailPoet Premium
|
||||
- mailpoet.com -- the primary site, and all of it subdomains, e.g. [account.mailpoet.com](https://account.mailpoet.com/)
|
||||
|
||||
For more targets, see the `In Scope` section on [HackerOne](https://hackerone.com/automattic).
|
||||
|
||||
@ -26,12 +26,12 @@ _Please note that the **WordPress software is a separate entity** from Automatti
|
||||
|
||||
We're committed to working with security researchers to resolve the vulnerabilities they discover. You can help us by following these guidelines:
|
||||
|
||||
* Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines).
|
||||
* Pen-testing Production:
|
||||
* Please **setup a local environment** instead whenever possible. Most of our code is open source (see above).
|
||||
* If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC.
|
||||
* **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels.
|
||||
* To be eligible for a bounty, please follow all of these guidelines.
|
||||
* Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability.
|
||||
- Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines).
|
||||
- Pen-testing Production:
|
||||
- Please **setup a local environment** instead whenever possible. Most of our code is open source (see above).
|
||||
- If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC.
|
||||
- **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels.
|
||||
- To be eligible for a bounty, please follow all of these guidelines.
|
||||
- Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability.
|
||||
|
||||
We also expect you to comply with all applicable laws. You're responsible to pay any taxes associated with your bounties.
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -3,7 +3,7 @@
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
name: "CodeQL"
|
||||
name: 'CodeQL'
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Contributing
|
||||
|
||||
## PHP Code
|
||||
|
||||
- Two spaces indentation.
|
||||
- Space between keyword (if, for, switch...) and left bracket
|
||||
- CamelCase for classes.
|
||||
@ -15,20 +16,24 @@
|
||||
- Cover your code in tests.
|
||||
|
||||
## SCSS Code
|
||||
|
||||
- camelCase for file name
|
||||
- Components files are prefixed with underscore, to indicate, that they aren't compiled separately.
|
||||
|
||||
## JS Code
|
||||
|
||||
- Javascript code should follow the [Airbnb style guide](https://github.com/airbnb/javascript).
|
||||
- Prefer named export before default export in JS and TS files
|
||||
|
||||
## Disabling linting rules
|
||||
|
||||
- we want to avoid using `eslint-disable`
|
||||
- if we have to use it we need to use a comment explaining why do we need it:
|
||||
`/* eslint-disable no-new -- this class has a side-effect in the constructor and it's a library's. */`
|
||||
`/* eslint-disable no-new -- this class has a side-effect in the constructor and it's a library's. */`
|
||||
- for PHP we do the same with the exception `// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps` which for now doesn’t require an explanation
|
||||
|
||||
## Git flow
|
||||
|
||||
- Do not commit to master.
|
||||
- Open a short-living feature branch.
|
||||
- Open a pull request.
|
||||
@ -39,12 +44,13 @@
|
||||
- Wait for review from another developer.
|
||||
|
||||
## Issues creation
|
||||
|
||||
- Issues are managed on Jira.
|
||||
- Discuss issues on public Slack chats, discuss code in pull requests.
|
||||
- Open a small Jira issue only when it has been discussed.
|
||||
|
||||
## Migration from IdiORM to Doctrine
|
||||
|
||||
MailPoet used to use [IdiORM](https://github.com/j4mie/idiorm) as its object-relational mapper (ORM), but the project was abandoned a while ago, so we started a migration to [Doctrine](https://www.doctrine-project.org/). This is a significant effort that has been going on for quite some time. Although you will still see parts of the code that use IdioORM, we ask that all new code be added using Doctrine instead.
|
||||
|
||||
All IdioORM models live in [mailpoet/lib/Models](https://github.com/mailpoet/mailpoet/tree/master/mailpoet/lib/Models), should be considered deprecated and shouldn't be used by new code. We are moving everything to Doctrine entities and some auxiliary code when needed. You can find Doctrine entities in [mailpoet/lib/Entities](https://github.com/mailpoet/mailpoet/tree/master/mailpoet/lib/Entities).
|
||||
|
||||
|
29
README.md
29
README.md
@ -1,17 +1,19 @@
|
||||
# MailPoet
|
||||
|
||||
The **MailPoet** plugin monorepo.
|
||||
|
||||
To use our Docker-based development environment (recommended), continue with the steps below.
|
||||
If you'd like to use the plugin code directly, see details in [the plugin's readme](mailpoet/README.md).
|
||||
|
||||
## 🔌 Initial setup
|
||||
1) Run `./do setup` to pull everything and install necessary dependencies.
|
||||
2) Add secrets to `.env` files in `mailpoet` and `mailpoet-premium` directories. Go to the Secret Store and look for "MailPoet: plugin .env"
|
||||
3) Run `./do start` to start the stack.
|
||||
4) Go to http://localhost:8888 to see the dashboard of the dev environment.
|
||||
|
||||
1. Run `./do setup` to pull everything and install necessary dependencies.
|
||||
2. Add secrets to `.env` files in `mailpoet` and `mailpoet-premium` directories. Go to the Secret Store and look for "MailPoet: plugin .env"
|
||||
3. Run `./do start` to start the stack.
|
||||
4. Go to http://localhost:8888 to see the dashboard of the dev environment.
|
||||
|
||||
## 🔍 PHPStorm setup for XDebug
|
||||
|
||||
In `Languages & Preferences > PHP > Servers` set path mappings:
|
||||
|
||||
```shell
|
||||
@ -28,6 +30,7 @@ To use XDebug inside the **cron**, you need to pass a URL argument `&XDEBUG_TRIG
|
||||
Alternatively, you can add `XDEBUG_TRIGGER: yes` to the `wordpress` service in `docker-compose.yml` and restart it (which will run XDebug also for all other requests).
|
||||
|
||||
## Xdebug develop mode
|
||||
|
||||
[Xdebug develop mode](https://xdebug.org/docs/develop) is disabled by default because it causes performance issues due to conflicts with the DI container.
|
||||
|
||||
It can be enabled when needed using the `XDEBUG_MODE` environment variable. For example, it is possible to enable it by adding the following to `docker-compose.override.yml`:
|
||||
@ -38,12 +41,15 @@ environment:
|
||||
```
|
||||
|
||||
## 💾 NFS volume sharing for Mac
|
||||
|
||||
NFS volumes can bring more stability and performance on Docker for Mac. To setup NFS volume sharing run:
|
||||
|
||||
```shell
|
||||
sudo sh dev/mac-nfs-setup.sh
|
||||
```
|
||||
|
||||
Then create a Docker Compose override file with NFS settings and restart containers:
|
||||
|
||||
```shell
|
||||
cp docker-compose.override.macos-sample.yml docker-compose.override.yml
|
||||
|
||||
@ -55,18 +61,22 @@ docker-compose up -d
|
||||
outside your `Documents` folder, otherwise you may run into [file permission issues](https://objekt.click/2019/11/docker-the-problem-with-macos-catalina/).
|
||||
|
||||
# 🐶 Husky
|
||||
|
||||
We use [Husky](https://github.com/typicode/husky) to run automated checks in pre-commit hooks.
|
||||
|
||||
In case you're using [NVM](https://github.com/nvm-sh/nvm) for Node version management you may
|
||||
need to create or update your `~/.huskyrc` file with:
|
||||
|
||||
```sh
|
||||
# This loads nvm.sh and sets the correct PATH before running the hooks:
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
```
|
||||
|
||||
Without it, you may experience errors in some Git clients.
|
||||
|
||||
## 🕹 Commands
|
||||
|
||||
The `./do` script define aliases for most of the commands you will need while working on plugins:
|
||||
|
||||
```shell
|
||||
@ -88,11 +98,14 @@ Options:
|
||||
You can access this help in your command line running `./do` without parameters.
|
||||
|
||||
## ✉️ Adding new templates to the plugin
|
||||
|
||||
[Read the article.](https://mailpoet.atlassian.net/wiki/spaces/MAILPOET/pages/629374977/Adding+new+templates+to+the+plugin)
|
||||
|
||||
## 🚥 Testing with PHP 7.4 or PHP 8.0
|
||||
|
||||
To switch the environment to PHP 7.4/8.0:
|
||||
1) Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile:
|
||||
|
||||
1. Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile:
|
||||
|
||||
```yaml
|
||||
wordpress:
|
||||
@ -100,11 +113,13 @@ To switch the environment to PHP 7.4/8.0:
|
||||
context: .
|
||||
dockerfile: docker/php74/Dockerfile # OR docker/php80/Dockerfile
|
||||
```
|
||||
3) Run `docker-compose build wordpress`.
|
||||
4) Start the stack with `./do start`.
|
||||
|
||||
2. Run `docker-compose build wordpress`.
|
||||
3. Start the stack with `./do start`.
|
||||
|
||||
To switch back to PHP 8.1 remove what was added in 1) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container,
|
||||
and start the stack using `./do start`.
|
||||
|
||||
## ✅ TODO
|
||||
|
||||
- install woo commerce, members and other useful plugins by default
|
||||
|
@ -8,7 +8,7 @@ thanks for understanding.
|
||||
- [Support](https://www.mailpoet.com/support)
|
||||
- [Feature Requests](https://feedback.mailpoet.com)
|
||||
|
||||
*DO NOT* use the issue tracker to ask questions;
|
||||
_DO NOT_ use the issue tracker to ask questions;
|
||||
use the links above for that.
|
||||
Questions posed to the issue tracker will be closed.
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>MailPoet dev environment</title>
|
||||
<link rel="icon" href="favicon.png" />
|
||||
<style>
|
||||
body {
|
||||
background: #ffe0d0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol';
|
||||
}
|
||||
p {
|
||||
color: #071c6d;
|
||||
@ -38,9 +40,9 @@
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<a href="/"><img src="logo.svg" /></a>
|
||||
<p>Dev environment</p>
|
||||
<table>
|
||||
@ -66,11 +68,17 @@
|
||||
<tr>
|
||||
<td>💾</td>
|
||||
<td>
|
||||
<a href="http://localhost:8081?server=db&mysql=wordpress&username=wordpress">Adminer</a>
|
||||
<a
|
||||
href="http://localhost:8081?server=db&mysql=wordpress&username=wordpress"
|
||||
>Adminer</a
|
||||
>
|
||||
</td>
|
||||
<td>DB management</td>
|
||||
<td>
|
||||
<a href="http://localhost:8081?server=db&mysql=wordpress&username=wordpress">http://localhost:8081</a>
|
||||
<a
|
||||
href="http://localhost:8081?server=db&mysql=wordpress&username=wordpress"
|
||||
>http://localhost:8081</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -81,5 +89,5 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,33 +4,39 @@ This is a place where we put documentation for developers who want to build an e
|
||||
If you are a user looking for a user guide please visit our [knowledge base](https://kb.mailpoet.com/).
|
||||
|
||||
## MailPoet API
|
||||
|
||||
MailPoet API is the officially supported way to integrate with the MailPoet 3 plugin. It focuses on functionality for managing subscribers.
|
||||
Developers integrating MailPoet functionality in their own plugins or projects are strongly discouraged against using other functions and classes within MailPoet codebase! We are continually refactoring as part of our rapid development process, and backward compatibility is not guaranteed.
|
||||
|
||||
### Basics
|
||||
|
||||
MailPoet API is distributed within MailPoet3 plugin and it is implemented as a PHP class.
|
||||
Currently supported version is `v1`.
|
||||
|
||||
### Instantiation
|
||||
|
||||
```php
|
||||
if (class_exists(\MailPoet\API\API::class)) {
|
||||
$mailpoet_api = \MailPoet\API\API::MP('v1');
|
||||
}
|
||||
```
|
||||
|
||||
Class `\MailPoet\API\API` becomes available once MailPoet plugin is loaded by WordPress.
|
||||
|
||||
### Available API Methods
|
||||
* [Add List (addList)](api_methods/AddList.md)
|
||||
* [Add Subscriber (addSubscriber)](api_methods/AddSubscriber.md)
|
||||
* [Add Subscriber Field (addSubscriberField)](api_methods/AddSubscriberField.md)
|
||||
* [Get Lists (getLists)](api_methods/GetLists.md)
|
||||
* [Get Subscriber (getSubscriber)](api_methods/GetSubscriber.md)
|
||||
* [Get Subscriber Fields (getSubscriberFields)](api_methods/GetSubscriberFields.md)
|
||||
* [Is Setup Complete (isSetupComplete)](api_methods/IsSetupComplete.md)
|
||||
* [Subscribe to List (subscribeToList)](api_methods/SubscribeToList.md)
|
||||
* [Subscribe to List (subscribeToLists)](api_methods/SubscribeToLists.md)
|
||||
* [Unsubscribe from List (unsubscribeFromList)](api_methods/UnsubscribeFromList.md)
|
||||
* [Unsubscribe from Lists (unsubscribeFromLists)](api_methods/UnsubscribeFromLists.md)
|
||||
|
||||
- [Add List (addList)](api_methods/AddList.md)
|
||||
- [Add Subscriber (addSubscriber)](api_methods/AddSubscriber.md)
|
||||
- [Add Subscriber Field (addSubscriberField)](api_methods/AddSubscriberField.md)
|
||||
- [Get Lists (getLists)](api_methods/GetLists.md)
|
||||
- [Get Subscriber (getSubscriber)](api_methods/GetSubscriber.md)
|
||||
- [Get Subscriber Fields (getSubscriberFields)](api_methods/GetSubscriberFields.md)
|
||||
- [Is Setup Complete (isSetupComplete)](api_methods/IsSetupComplete.md)
|
||||
- [Subscribe to List (subscribeToList)](api_methods/SubscribeToList.md)
|
||||
- [Subscribe to List (subscribeToLists)](api_methods/SubscribeToLists.md)
|
||||
- [Unsubscribe from List (unsubscribeFromList)](api_methods/UnsubscribeFromList.md)
|
||||
- [Unsubscribe from Lists (unsubscribeFromLists)](api_methods/UnsubscribeFromLists.md)
|
||||
|
||||
### Usage examples
|
||||
|
||||
You can check some basic examples [here](UsageExamples.md).
|
||||
|
@ -1,9 +1,11 @@
|
||||
[back to readme](Readme.md)
|
||||
|
||||
# Usage Examples
|
||||
|
||||
Common usage is a rendering of a subscription form and processing it.
|
||||
|
||||
## Fetching data for a subscription form
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
@ -18,6 +20,7 @@ if (class_exists(\MailPoet\API\API::class)) {
|
||||
```
|
||||
|
||||
## Processing a subscription form
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
|
@ -9,14 +9,15 @@ In MailPoet, subscribers are organized into lists. This method provides function
|
||||
It returns the new list. See [Get Lists](GetLists.md) for a list data structure description.
|
||||
|
||||
## Arguments
|
||||
|
||||
### `$list` (required)
|
||||
|
||||
An associative array which contains list data.
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| ---------------------- | ------------ | --------- | -------------------------- |
|
||||
| name (required) | string | 90 chars | A name of the list. |
|
||||
| description (optional) | string\|null| 250 chars | A description of the list. |
|
||||
| description (optional) | string\|null | 250 chars | A description of the list. |
|
||||
|
||||
## Error handling
|
||||
|
||||
@ -28,7 +29,7 @@ An exception of base class `\Exception` can be thrown when something unexpected
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| --- | --- |
|
||||
| ---- | -------------------------------------------- |
|
||||
| 14 | Missing list name |
|
||||
| 15 | Trying to create a list that already exists |
|
||||
| 16 | The list couldn’t be created in the database |
|
||||
|
@ -8,9 +8,9 @@ This method allows a subscriber to be created, adds them into lists, and handles
|
||||
|
||||
If sign-up confirmation (double opt-in) is enabled in the MailPoet settings a subscriber is created with status `unconfirmed` otherwise the status is set to `subscribed`.
|
||||
|
||||
- *A confirmation email* is an email which is sent to a subscriber so that they can confirm his subscription. It is sent only if sign-up confirmation is enabled in the MailPoet settings.
|
||||
- *A welcome email* is an automatic email which is sent to a new subscriber. This email is scheduled only if sign-up confirmation is disabled and a welcome email is configured for some of given lists. In case of required sign-up confirmation, it is scheduled later after a subscriber confirms the subscription.
|
||||
- *An admin notification email* is sent to the site admin to inform them about a new subscription. It is sent only if the notification feature is enabled in the MailPoet setting.
|
||||
- _A confirmation email_ is an email which is sent to a subscriber so that they can confirm his subscription. It is sent only if sign-up confirmation is enabled in the MailPoet settings.
|
||||
- _A welcome email_ is an automatic email which is sent to a new subscriber. This email is scheduled only if sign-up confirmation is disabled and a welcome email is configured for some of given lists. In case of required sign-up confirmation, it is scheduled later after a subscriber confirms the subscription.
|
||||
- _An admin notification email_ is sent to the site admin to inform them about a new subscription. It is sent only if the notification feature is enabled in the MailPoet setting.
|
||||
|
||||
All these emails can be disabled using `$options`.
|
||||
|
||||
@ -21,30 +21,33 @@ There might be other `\Exceptions` because of some invalid input data such a inv
|
||||
It returns a new subscriber. See [Get Subscriber](GetSubscriber.md) for a subscriber data structure.
|
||||
|
||||
## Arguments
|
||||
|
||||
### `$subscriber` (required)
|
||||
|
||||
An associative array containing subscriber data which contains default properties (email, first_name, last_name) and custom subscriber fields which were defined in MailPoet.
|
||||
It has to contain an email and all required custom fields. To get defined custom fields see [Get Subscriber Fields](GetSubscriberFields.md)
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| -------------------------- | ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| email (required) | string | 150 chars | a valid email address |
|
||||
| first_name (optional) | string/null | 255 chars | Fist name of the subscriber. |
|
||||
| last_name (optional) | string/null | 255 chars | Last name of the subscriber. |
|
||||
| cf_* (optional/required) | string/boolean/null | 65K chars | A custom field (see [Get Subscriber Fields](GetSubscriberFields.md)). <br> If a custom field is a checkbox, send truthy or falsy value (`true`/`false, `1`/`0` or `"1"`\`"0"`). |
|
||||
| cf\_\* (optional/required) | string/boolean/null | 65K chars | A custom field (see [Get Subscriber Fields](GetSubscriberFields.md)). <br> If a custom field is a checkbox, send truthy or falsy value (`true`/`false, `1`/`0`or`"1"`\`"0"`). |
|
||||
|
||||
### `$list_ids` (optional)
|
||||
|
||||
An array containing list ids into which subscriber will be added.
|
||||
In case that the list is empty a subscriber will be created; but sending a confirmation email, notification email and scheduling welcome email will be skipped.
|
||||
|
||||
### `$options` (optional)
|
||||
|
||||
All options are optional. If omitted a default value is used.
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| ---------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| send_confirmation_email | boolean | true | Can be used to disable a confirmation email. Otherwise, a confirmation email is sent as described above. It is strongly recommended to keep this option set to `true` so that MailPoet settings for sign-up confirmation are respected. Turning it to `false` might lead that subscriber to be added as `unconfirmed`. |
|
||||
| schedule_welcome_email | boolean | true | Can be used to disable a welcome email. Otherwise, a welcome email is scheduled as described above.|
|
||||
| skip_subscriber_notification | boolean | false | Can be used to disable an admin notification email. Otherwise, an admin notification email is sent as described above.|
|
||||
| schedule_welcome_email | boolean | true | Can be used to disable a welcome email. Otherwise, a welcome email is scheduled as described above. |
|
||||
| skip_subscriber_notification | boolean | false | Can be used to disable an admin notification email. Otherwise, an admin notification email is sent as described above. |
|
||||
|
||||
## Error handling
|
||||
|
||||
@ -56,7 +59,7 @@ An exception of base class `\Exception` can be thrown when something unexpected
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| --- | --- |
|
||||
| ---- | -------------------------------------------------- |
|
||||
| 11 | Missing email address |
|
||||
| 12 | Trying to create a subscriber that already exists |
|
||||
| 13 | The subscriber couldn’t be created in the database |
|
||||
|
@ -12,7 +12,7 @@ See [Subscriber Fields for more details](GetSubscriberFields.md)
|
||||
### `$data` (required)
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------- |
|
||||
| name (required) | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. |
|
||||
| type (required) | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` |
|
||||
| params (optional) | array | - | Contains various information, see examples below. |
|
||||
@ -23,40 +23,40 @@ Params array differs for each type.
|
||||
The common properties for all types:
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| -------- | ------ | ------------------------------------------------------------------------------------------- |
|
||||
| required | string | Indicates if the value must be provided for each subscriber. Possible values are: "1" or "" |
|
||||
| label | string | Label used for displaying the field to the end user. |
|
||||
|
||||
#### `$params` for text, textarea types
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| -------- | ------ | ------------------------------------------------------------------------------------------- |
|
||||
| validate | string | Can be used for validating input values. Possible values are: `number`, `alphanum`, `phone` |
|
||||
|
||||
#### `$params` for checkbox types
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| -------- | ----- | ------------------------------------------------------------ |
|
||||
| values | array | Same array as for radio type. Must contain exactly 1 element |
|
||||
|
||||
#### `$params` for radio, select types
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| -------- | ----- | --------------------------------------------------------------------------------------------------- |
|
||||
| values | array | Contains a list of options. Each element must contain a string `value` and can contain `is_checked` |
|
||||
|
||||
#### `$params` for date type
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| date_type | string | Possible values are: Values: `year_month_day`, `year_month`, `month`, `day` |
|
||||
| date_format | string | Values: for year_month_day: `MM/DD/YYYY`, `DD/MM/YYYY`, `YYYY/MM/DD`, for year_month: `YYYY/MM`, `MM/YY`, for year: `YYYY`, for month: `MM` |
|
||||
|
||||
## Response
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| id | string | 11 chars |Field Id |
|
||||
| ---------- | ------------ | -------- | ------------------------------------------------------------------------------------------------- |
|
||||
| id | string | 11 chars | Field Id |
|
||||
| name | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. |
|
||||
| type | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` |
|
||||
| params | array | - | Contains various information, see examples below. |
|
||||
@ -73,7 +73,7 @@ An exception of base class `\Exception` can be thrown when something unexpected
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| --- | --- |
|
||||
| ---- | ---------------------------------------------------------------------------------- |
|
||||
| 1 | The subscriber couldn’t be created in the database |
|
||||
| 1001 | Missing a mandatory field in the `$data` argument |
|
||||
| 1002 | A mandatory field in the `$data` argument has wrong type |
|
||||
|
@ -9,7 +9,7 @@ In MailPoet, subscribers are organized into lists. This method returns an array
|
||||
### A list data structure
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| ----------- | ------------ | --------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| id | string | 11 chars | Id of the list |
|
||||
| name | string | 90 chars | Name of the list |
|
||||
| type | string | - | Type of the list. Currently, there is only one supported value: `default` |
|
||||
@ -19,6 +19,7 @@ In MailPoet, subscribers are organized into lists. This method returns an array
|
||||
| deleted_at | string\|null | - | This property is not null only when the list is in the trash. It contains UTC time in 'Y-m-d H:i:s' format. |
|
||||
|
||||
### Response Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
[
|
||||
|
@ -7,15 +7,17 @@
|
||||
This method throws an `\Exception` in the event a subscriber with a given email address doesn’t exist.
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ----------------- | ------ | --------------------- |
|
||||
| $subscriber_email | string | a valid email address |
|
||||
|
||||
## A subscriber data structure
|
||||
|
||||
### Subscriber
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| ------------------------ | ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| id | string | 11 chars | Id of the subscriber |
|
||||
| wp_user_id | string\|null | 20 chars | Id of a WordPress user associated with the subscriber |
|
||||
| is_woocommerce_user | string | - | A flag telling whether the user is also a WooCommerce customer. Possible values are: `1`, `0` |
|
||||
@ -30,14 +32,15 @@ This method throws an `\Exception` in the event a subscriber with a given email
|
||||
| updated_at | string | - | UTC time of last update in 'Y-m-d H:i:s' format |
|
||||
| deleted_at | string\|null | - | This property in not null in case that list is in trash and contains UTC time in 'Y-m-d H:i:s' format. |
|
||||
| unconfirmed_data | string\|null | 65K chars | May contain serialized subscriber data in case when there are pending changes waiting for a confirmation from a subscriber |
|
||||
| source | string\|null | - | Possible values: `form`,`imported`,`administrator`,`api`,`wordpress_user`,`woocommerce_user`,`woocommerce_checkout`,`unknown`)
|
||||
| source | string\|null | - | Possible values: `form`,`imported`,`administrator`,`api`,`wordpress_user`,`woocommerce_user`,`woocommerce_checkout`,`unknown`) |
|
||||
| count_confirmations | string | 11 chars | Counter for confirmation emails |
|
||||
| subscriptions | array | - | List of subcriber subscriptions |
|
||||
| cf_{custom_field['id']}| string | 65K chars | A custom subscriber field value (see [Get Subscriber Fields](GetSubscriberFields.md)|
|
||||
| cf\_{custom_field['id']} | string | 65K chars | A custom subscriber field value (see [Get Subscriber Fields](GetSubscriberFields.md) |
|
||||
|
||||
### Subscriber's subscription
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------ |
|
||||
| id | string | 11 chars | Id of relation |
|
||||
| subscriber_id | string | 11 chars | Id of subscriber |
|
||||
| segment_id | string | 11 chars | Id of a list |
|
||||
@ -46,6 +49,7 @@ This method throws an `\Exception` in the event a subscriber with a given email
|
||||
| updated_at | string | - | UTC time of last update in 'Y-m-d H:i:s' format |
|
||||
|
||||
### Response Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
[
|
||||
@ -98,5 +102,5 @@ An exception of base class `\Exception` can be thrown when something unexpected
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| --- | --- |
|
||||
| ---- | -------------------------------------------- |
|
||||
| 4 | Asking for a subscriber that does not exist. |
|
||||
|
@ -12,13 +12,14 @@ See also [addSubscriberField function.](AddSubscriberField.md)
|
||||
## Subscriber Field
|
||||
|
||||
| Property | Type | Limits | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| id | string | 11 chars |Field Id |
|
||||
| -------- | ------ | -------- | ------------------------------------------------------------------------------------------------- |
|
||||
| id | string | 11 chars | Field Id |
|
||||
| name | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. |
|
||||
| type | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` |
|
||||
| params | array | - | Contains various information, see examples below. |
|
||||
|
||||
## Response Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
[
|
||||
|
@ -6,29 +6,33 @@
|
||||
|
||||
This method allows adding an existing subscriber into lists and handles confirmation email and admin notification email sending.
|
||||
|
||||
- *A confirmation email* is an email which is sent to a subscriber so they can confirm their subscription. It is sent only if sign-up confirmation is enabled in MailPoet settings and subscriber has not received any confirmation email yet.
|
||||
- *A welcome email* is an automatic email which is sent to a new subscriber. This email is scheduled only if sign-up confirmation is disabled and some welcome email is configured for some of given lists.
|
||||
- *An admin notification email* is sent to the site admin to inform them about a new subscription. It is sent only if the notification feature is enabled in the MailPoet setting.
|
||||
- _A confirmation email_ is an email which is sent to a subscriber so they can confirm their subscription. It is sent only if sign-up confirmation is enabled in MailPoet settings and subscriber has not received any confirmation email yet.
|
||||
- _A welcome email_ is an automatic email which is sent to a new subscriber. This email is scheduled only if sign-up confirmation is disabled and some welcome email is configured for some of given lists.
|
||||
- _An admin notification email_ is sent to the site admin to inform them about a new subscription. It is sent only if the notification feature is enabled in the MailPoet setting.
|
||||
|
||||
All these emails can be disabled using `$options`.
|
||||
|
||||
It returns a subscriber. See [Get Subscriber](GetSubscriber.md) for a subscriber data structure.
|
||||
|
||||
## Arguments
|
||||
|
||||
### string `$subscriber_id` (required)
|
||||
|
||||
An id or an email address. An `\Exception` is thrown when the value doesn't match any subscriber.
|
||||
|
||||
### array `$list_ids` (required)
|
||||
|
||||
An array of list ids. An `\Exception` is thrown if any of list ids are invalid. In such a case the subscriber isn't added to any list.
|
||||
|
||||
### array `$options` (optional)
|
||||
|
||||
All options are optional. If omitted, a default value is used.
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| send_confirmation_email | boolean | true | Can be used to disable confirmation email. Otherwise, a confirmation email is sent as described above.|
|
||||
| schedule_welcome_email | boolean | true | Can be used to disable welcome email. Otherwise, a welcome email is scheduled as described above.|
|
||||
| skip_subscriber_notification | boolean | false | Can be used to disable an admin notification email. Otherwise, an admin notification email is sent as described above.|
|
||||
| ---------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| send_confirmation_email | boolean | true | Can be used to disable confirmation email. Otherwise, a confirmation email is sent as described above. |
|
||||
| schedule_welcome_email | boolean | true | Can be used to disable welcome email. Otherwise, a welcome email is scheduled as described above. |
|
||||
| skip_subscriber_notification | boolean | false | Can be used to disable an admin notification email. Otherwise, an admin notification email is sent as described above. |
|
||||
|
||||
## Error handling
|
||||
|
||||
@ -40,7 +44,7 @@ An exception of base class `\Exception` can be thrown when something unexpected
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| --- | --- |
|
||||
| ---- | ------------------------------------------------------- |
|
||||
| 3 | No lists provided |
|
||||
| 4 | Invalid subscriber that does not exist |
|
||||
| 5 | Invalid list that does not exist |
|
||||
|
@ -9,10 +9,13 @@ This method removes a subscriber from given lists.
|
||||
It returns a subscriber. See [Get Subscriber](GetSubscriber.md) for a subscriber data structure.
|
||||
|
||||
## Arguments
|
||||
|
||||
### string `$subscriber_id` (required)
|
||||
|
||||
An id or email of an existing subscriber. An `\Exception` is thrown when an id or email doesn't match any subscriber.
|
||||
|
||||
### array `$list_ids` (required)
|
||||
|
||||
An array of list ids. An `\Exception` is thrown if any of list ids are invalid. In such a case the subscriber remains subscribed to all lists.
|
||||
|
||||
## Error handling
|
||||
@ -25,7 +28,7 @@ An exception of base class `\Exception` can be thrown when something unexpected
|
||||
Codes description:
|
||||
|
||||
| Code | Description |
|
||||
| --- | --- |
|
||||
| ---- | --------------------------------------------------- |
|
||||
| 3 | No lists provided |
|
||||
| 4 | Invalid subscriber that does not exist |
|
||||
| 5 | Invalid list that does not exist |
|
||||
|
@ -22,16 +22,16 @@ volumes:
|
||||
driver_opts:
|
||||
type: nfs
|
||||
o: addr=host.docker.internal,nolock
|
||||
device: ":/System/Volumes/Data${PWD}/wordpress"
|
||||
device: ':/System/Volumes/Data${PWD}/wordpress'
|
||||
nfs-mailpoet:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: nfs
|
||||
o: addr=host.docker.internal,nolock
|
||||
device: ":/System/Volumes/Data${PWD}/mailpoet"
|
||||
device: ':/System/Volumes/Data${PWD}/mailpoet'
|
||||
nfs-mailpoet-premium:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: nfs
|
||||
o: addr=host.docker.internal,nolock
|
||||
device: ":/System/Volumes/Data${PWD}/mailpoet-premium"
|
||||
device: ':/System/Volumes/Data${PWD}/mailpoet-premium'
|
||||
|
@ -31,8 +31,8 @@ services:
|
||||
UID: ${UID:-1000}
|
||||
GID: ${GID:-1000}
|
||||
ports:
|
||||
- "8002:80"
|
||||
- "8083:8083" # Storybook port number, see package.json
|
||||
- '8002:80'
|
||||
- '8083:8083' # Storybook port number, see package.json
|
||||
depends_on:
|
||||
- db
|
||||
- smtp
|
||||
@ -43,17 +43,17 @@ services:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: wordpress
|
||||
PHP_IDE_CONFIG: "serverName=Mailpoet"
|
||||
COMPOSER_HOME: "/tmp/.composer"
|
||||
NPM_CONFIG_CACHE: "/tmp/.npm"
|
||||
PHP_IDE_CONFIG: 'serverName=Mailpoet'
|
||||
COMPOSER_HOME: '/tmp/.composer'
|
||||
NPM_CONFIG_CACHE: '/tmp/.npm'
|
||||
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"
|
||||
- "./mailpoet:/var/www/html/wp-content/plugins/mailpoet"
|
||||
- "./mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium"
|
||||
- "./templates:/var/www/templates"
|
||||
- './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'
|
||||
- './mailpoet:/var/www/html/wp-content/plugins/mailpoet'
|
||||
- './mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium'
|
||||
- './templates:/var/www/templates'
|
||||
|
||||
test_wordpress:
|
||||
container_name: mp-test-wp
|
||||
@ -64,7 +64,7 @@ services:
|
||||
UID: ${UID:-1000}
|
||||
GID: ${GID:-1000}
|
||||
ports:
|
||||
- "8003:80"
|
||||
- '8003:80'
|
||||
depends_on:
|
||||
- db
|
||||
- smtp
|
||||
@ -74,11 +74,11 @@ services:
|
||||
WORDPRESS_DB_HOST: db:3306
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: wordpress
|
||||
PHP_IDE_CONFIG: "serverName=Mailpoet"
|
||||
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"
|
||||
- './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'
|
||||
|
||||
smtp:
|
||||
container_name: mp-mailhog
|
||||
@ -88,9 +88,9 @@ services:
|
||||
MH_STORAGE: maildir
|
||||
MH_MAILDIR_PATH: /output
|
||||
volumes:
|
||||
- "./dev/data/mailhog:/output"
|
||||
- './dev/data/mailhog:/output'
|
||||
ports:
|
||||
- "8082:8025"
|
||||
- '8082:8025'
|
||||
|
||||
adminer:
|
||||
container_name: mp-adminer
|
||||
@ -98,9 +98,9 @@ services:
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "8081:8080"
|
||||
- '8081:8080'
|
||||
volumes:
|
||||
- "./dev/php.ini:/usr/local/etc/php/conf.d/custom.ini"
|
||||
- './dev/php.ini:/usr/local/etc/php/conf.d/custom.ini'
|
||||
|
||||
volumes:
|
||||
my-datavolume:
|
||||
|
@ -1,9 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:react/jsx-runtime",
|
||||
"prettier"
|
||||
],
|
||||
"extends": ["airbnb", "plugin:react/jsx-runtime", "prettier"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
@ -14,9 +10,7 @@
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"react-hooks"
|
||||
],
|
||||
"plugins": ["react-hooks"],
|
||||
"settings": {
|
||||
"import/resolver": "webpack"
|
||||
},
|
||||
@ -27,9 +21,12 @@
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
// Exceptions
|
||||
"import/extensions": 0, // we wouldn't be able to import jQuery without this line
|
||||
"jsx-a11y/anchor-is-valid": [ "error", {
|
||||
"components": [ "Link" ],
|
||||
"specialLink": [ "to" ]
|
||||
}]
|
||||
"jsx-a11y/anchor-is-valid": [
|
||||
"error",
|
||||
{
|
||||
"components": ["Link"],
|
||||
"specialLink": ["to"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,7 @@
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"react-hooks",
|
||||
"no-only-tests",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["react-hooks", "no-only-tests", "@typescript-eslint"],
|
||||
"settings": {
|
||||
"import/resolver": "webpack"
|
||||
},
|
||||
@ -49,19 +45,28 @@
|
||||
"import/prefer-default-export": 0, // we want to stop using default exports and start using named exports
|
||||
"react/destructuring-assignment": 0, // that would be too many changes to fix this one
|
||||
"prefer-destructuring": 0, // that would be too many changes to fix this one
|
||||
"jsx-a11y/label-has-for": [2, {
|
||||
"required": {"some": ["nesting", "id"]} // some of our labels are hidden and we cannot nest those
|
||||
}],
|
||||
"jsx-a11y/label-has-for": [
|
||||
2,
|
||||
{
|
||||
"required": { "some": ["nesting", "id"] } // some of our labels are hidden and we cannot nest those
|
||||
}
|
||||
],
|
||||
"jsx-a11y/anchor-is-valid": 0, // cannot fix this one, it would break wprdpress themes
|
||||
"jsx-a11y/label-has-associated-control": [ 2, {
|
||||
"jsx-a11y/label-has-associated-control": [
|
||||
2,
|
||||
{
|
||||
"either": "either" // control has to be either nested or associated via htmlFor
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/_stories/*.tsx"],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{ "devDependencies": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1,8 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb/legacy",
|
||||
"prettier"
|
||||
],
|
||||
"extends": ["airbnb/legacy", "prettier"],
|
||||
"env": {
|
||||
"amd": true,
|
||||
"browser": true
|
||||
|
@ -1,9 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:react/jsx-runtime",
|
||||
"prettier"
|
||||
],
|
||||
"extends": ["airbnb", "plugin:react/jsx-runtime", "prettier"],
|
||||
"env": {
|
||||
"amd": true,
|
||||
"browser": true,
|
||||
@ -16,10 +12,7 @@
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"react-hooks",
|
||||
"no-only-tests"
|
||||
],
|
||||
"plugins": ["react-hooks", "no-only-tests"],
|
||||
"settings": {
|
||||
"import/resolver": "webpack"
|
||||
},
|
||||
@ -36,13 +29,19 @@
|
||||
"import/prefer-default-export": 0, // we want to stop using default exports and start using named exports
|
||||
"react/destructuring-assignment": 0, // that would be too many changes to fix this one
|
||||
"prefer-destructuring": 0, // that would be too many changes to fix this one
|
||||
"jsx-a11y/label-has-for": [2, {
|
||||
"required": {"some": ["nesting", "id"]} // some of our labels are hidden and we cannot nest those
|
||||
}],
|
||||
"jsx-a11y/label-has-for": [
|
||||
2,
|
||||
{
|
||||
"required": { "some": ["nesting", "id"] } // some of our labels are hidden and we cannot nest those
|
||||
}
|
||||
],
|
||||
"jsx-a11y/anchor-is-valid": 0, // cannot fix this one, it would break wprdpress themes
|
||||
"jsx-a11y/label-has-associated-control": [ 2, {
|
||||
"jsx-a11y/label-has-associated-control": [
|
||||
2,
|
||||
{
|
||||
"either": "either" // control has to be either nested or associated via htmlFor
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@ -53,4 +52,3 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,7 @@
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"react-hooks",
|
||||
"no-only-tests",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["react-hooks", "no-only-tests", "@typescript-eslint"],
|
||||
"settings": {
|
||||
"import/resolver": "webpack",
|
||||
"import/core-modules": [
|
||||
@ -45,10 +41,7 @@
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"react/no-unstable-nested-components": [
|
||||
"error",
|
||||
{ "allowAsProps": true }
|
||||
],
|
||||
"react/no-unstable-nested-components": ["error", { "allowAsProps": true }],
|
||||
// PropTypes
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
@ -59,7 +52,7 @@
|
||||
// Exceptions
|
||||
"@typescript-eslint/no-explicit-any": "error", // make it an error instead of warning - we treat them the same, this is more visible
|
||||
"no-void": 0, // can conflict with @typescript-eslint/no-floating-promises
|
||||
"react/jsx-no-useless-fragment" : [
|
||||
"react/jsx-no-useless-fragment": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
@ -76,13 +69,19 @@
|
||||
"import/prefer-default-export": 0, // we want to stop using default exports and start using named exports
|
||||
"react/destructuring-assignment": 0, // that would be too many changes to fix this one
|
||||
"prefer-destructuring": 0, // that would be too many changes to fix this one
|
||||
"jsx-a11y/label-has-for": [2, {
|
||||
"required": {"some": ["nesting", "id"]} // some of our labels are hidden and we cannot nest those
|
||||
}],
|
||||
"jsx-a11y/label-has-for": [
|
||||
2,
|
||||
{
|
||||
"required": { "some": ["nesting", "id"] } // some of our labels are hidden and we cannot nest those
|
||||
}
|
||||
],
|
||||
"jsx-a11y/anchor-is-valid": 0, // cannot fix this one, it would break wprdpress themes
|
||||
"jsx-a11y/label-has-associated-control": [ 2, {
|
||||
"jsx-a11y/label-has-associated-control": [
|
||||
2,
|
||||
{
|
||||
"either": "either" // control has to be either nested or associated via htmlFor
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@ -96,7 +95,10 @@
|
||||
{
|
||||
"files": ["**/_stories/*.tsx"],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{ "devDependencies": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
const path = require('path');
|
||||
|
||||
const modulesDir = path.join( __dirname, '../node_modules' );
|
||||
const modulesDir = path.join(__dirname, '../node_modules');
|
||||
console.log('NODE', modulesDir);
|
||||
// Workaround for Emotion 11
|
||||
// https://github.com/storybookjs/storybook/pull/13300#issuecomment-783268111
|
||||
|
@ -6,4 +6,9 @@ import '../assets/dist/css/mailpoet-plugin.css';
|
||||
import '../assets/dist/css/mailpoet-form-editor.css';
|
||||
|
||||
addDecorator(withPerformance);
|
||||
addDecorator(story => <div className="wp-core-ui" id="wpbody"><div id="mailpoet-modal"></div>{story()}</div>);
|
||||
addDecorator((story) => (
|
||||
<div className="wp-core-ui" id="wpbody">
|
||||
<div id="mailpoet-modal"></div>
|
||||
{story()}
|
||||
</div>
|
||||
));
|
||||
|
@ -1,110 +1,106 @@
|
||||
{
|
||||
"plugins": [
|
||||
"stylelint-order",
|
||||
"stylelint-scss"
|
||||
'plugins': ['stylelint-order', 'stylelint-scss'],
|
||||
'customSyntax': 'postcss-scss',
|
||||
'rules':
|
||||
{
|
||||
'at-rule-empty-line-before':
|
||||
['always', { except: ['first-nested', 'blockless-after-blockless'] }],
|
||||
'at-rule-name-case': 'lower',
|
||||
'at-rule-semicolon-newline-after': 'always',
|
||||
'block-closing-brace-empty-line-before': 'never',
|
||||
'block-closing-brace-newline-after': 'always',
|
||||
'block-closing-brace-newline-before': 'always-multi-line',
|
||||
'block-closing-brace-space-before': 'always-single-line',
|
||||
'block-no-empty': true,
|
||||
'block-opening-brace-newline-after': 'always-multi-line',
|
||||
'block-opening-brace-space-after': 'always-single-line',
|
||||
'block-opening-brace-space-before': 'always',
|
||||
'color-hex-case': 'lower',
|
||||
'color-hex-length': 'short',
|
||||
'color-no-invalid-hex': true,
|
||||
'comment-no-empty': true,
|
||||
'comment-whitespace-inside': 'always',
|
||||
'declaration-bang-space-after': 'never',
|
||||
'declaration-bang-space-before': 'always',
|
||||
'declaration-block-no-duplicate-properties':
|
||||
[true, { ignore: ['consecutive-duplicates-with-different-values'] }],
|
||||
'declaration-block-no-redundant-longhand-properties':
|
||||
[true, { ignoreShorthands: [/flex/, /grid/] }],
|
||||
'declaration-block-semicolon-newline-after': 'always-multi-line',
|
||||
'declaration-block-semicolon-space-after': 'always-single-line',
|
||||
'declaration-block-semicolon-space-before': 'never',
|
||||
'declaration-block-single-line-max-declarations': 1,
|
||||
'declaration-colon-newline-after': 'always-multi-line',
|
||||
'declaration-colon-space-after': 'always-single-line',
|
||||
'declaration-colon-space-before': 'never',
|
||||
'declaration-empty-line-before': 'never',
|
||||
'font-family-no-duplicate-names': true,
|
||||
'function-comma-space-after': 'always-single-line',
|
||||
'function-comma-space-before': 'never',
|
||||
'function-max-empty-lines': 0,
|
||||
'function-name-case': 'lower',
|
||||
'function-parentheses-newline-inside': 'always-multi-line',
|
||||
'function-parentheses-space-inside': 'never-single-line',
|
||||
'function-url-quotes': 'always',
|
||||
'function-whitespace-after': 'always',
|
||||
'indentation': 2,
|
||||
'keyframe-declaration-no-important': true,
|
||||
'length-zero-no-unit': true,
|
||||
'max-empty-lines': 1,
|
||||
'media-feature-colon-space-after': 'always',
|
||||
'media-feature-colon-space-before': 'never',
|
||||
'media-feature-name-case': 'lower',
|
||||
'media-feature-name-no-unknown': true,
|
||||
'media-feature-parentheses-space-inside': 'never',
|
||||
'media-feature-range-operator-space-after': 'always',
|
||||
'media-query-list-comma-newline-after': 'always-multi-line',
|
||||
'media-query-list-comma-space-after': 'always-single-line',
|
||||
'media-query-list-comma-space-before': 'never',
|
||||
'no-duplicate-selectors': true,
|
||||
'no-eol-whitespace': true,
|
||||
'no-extra-semicolons': true,
|
||||
'no-missing-end-of-source-newline': true,
|
||||
'number-leading-zero': 'never',
|
||||
'number-no-trailing-zeros': true,
|
||||
'order/properties-alphabetical-order': true,
|
||||
'property-case': 'lower',
|
||||
'property-no-unknown': true,
|
||||
'rule-empty-line-before':
|
||||
[
|
||||
'always-multi-line',
|
||||
{ except: ['first-nested'], ignore: ['after-comment'] },
|
||||
],
|
||||
"customSyntax": "postcss-scss",
|
||||
"rules": {
|
||||
"at-rule-empty-line-before": ["always", {
|
||||
except: ["first-nested", "blockless-after-blockless"],
|
||||
}],
|
||||
"at-rule-name-case": "lower",
|
||||
"at-rule-semicolon-newline-after": "always",
|
||||
"block-closing-brace-empty-line-before": "never",
|
||||
"block-closing-brace-newline-after": "always",
|
||||
"block-closing-brace-newline-before": "always-multi-line",
|
||||
"block-closing-brace-space-before": "always-single-line",
|
||||
"block-no-empty": true,
|
||||
"block-opening-brace-newline-after": "always-multi-line",
|
||||
"block-opening-brace-space-after": "always-single-line",
|
||||
"block-opening-brace-space-before": "always",
|
||||
"color-hex-case": "lower",
|
||||
"color-hex-length": "short",
|
||||
"color-no-invalid-hex": true,
|
||||
"comment-no-empty": true,
|
||||
"comment-whitespace-inside": "always",
|
||||
"declaration-bang-space-after": "never",
|
||||
"declaration-bang-space-before": "always",
|
||||
"declaration-block-no-duplicate-properties": [true, {
|
||||
ignore: ["consecutive-duplicates-with-different-values"],
|
||||
}],
|
||||
"declaration-block-no-redundant-longhand-properties": [true, {
|
||||
ignoreShorthands: [/flex/, /grid/]
|
||||
}],
|
||||
"declaration-block-semicolon-newline-after": "always-multi-line",
|
||||
"declaration-block-semicolon-space-after": "always-single-line",
|
||||
"declaration-block-semicolon-space-before": "never",
|
||||
"declaration-block-single-line-max-declarations": 1,
|
||||
"declaration-colon-newline-after": "always-multi-line",
|
||||
"declaration-colon-space-after": "always-single-line",
|
||||
"declaration-colon-space-before": "never",
|
||||
"declaration-empty-line-before": "never",
|
||||
"font-family-no-duplicate-names": true,
|
||||
"function-comma-space-after": "always-single-line",
|
||||
"function-comma-space-before": "never",
|
||||
"function-max-empty-lines": 0,
|
||||
"function-name-case": "lower",
|
||||
"function-parentheses-newline-inside": "always-multi-line",
|
||||
"function-parentheses-space-inside": "never-single-line",
|
||||
"function-url-quotes": "always",
|
||||
"function-whitespace-after": "always",
|
||||
"indentation": 2,
|
||||
"keyframe-declaration-no-important": true,
|
||||
"length-zero-no-unit": true,
|
||||
"max-empty-lines": 1,
|
||||
"media-feature-colon-space-after": "always",
|
||||
"media-feature-colon-space-before": "never",
|
||||
"media-feature-name-case": "lower",
|
||||
"media-feature-name-no-unknown": true,
|
||||
"media-feature-parentheses-space-inside": "never",
|
||||
"media-feature-range-operator-space-after": "always",
|
||||
"media-query-list-comma-newline-after": "always-multi-line",
|
||||
"media-query-list-comma-space-after": "always-single-line",
|
||||
"media-query-list-comma-space-before": "never",
|
||||
"no-duplicate-selectors": true,
|
||||
"no-eol-whitespace": true,
|
||||
"no-extra-semicolons": true,
|
||||
"no-missing-end-of-source-newline": true,
|
||||
"number-leading-zero": "never",
|
||||
"number-no-trailing-zeros": true,
|
||||
"order/properties-alphabetical-order": true,
|
||||
"property-case": "lower",
|
||||
"property-no-unknown": true,
|
||||
"rule-empty-line-before": ["always-multi-line", {
|
||||
except: ["first-nested"],
|
||||
ignore: ["after-comment"]
|
||||
}],
|
||||
"scss/at-rule-no-unknown": true,
|
||||
"scss/dollar-variable-colon-space-after": "always",
|
||||
"scss/dollar-variable-colon-space-before": "never",
|
||||
"scss/operator-no-newline-after": true,
|
||||
"scss/operator-no-newline-before": true,
|
||||
"scss/operator-no-unspaced": true,
|
||||
"scss/selector-no-redundant-nesting-selector": true,
|
||||
"selector-attribute-brackets-space-inside": "never",
|
||||
"selector-attribute-operator-space-after": "never",
|
||||
"selector-attribute-operator-space-before": "never",
|
||||
"selector-combinator-space-after": "always",
|
||||
"selector-combinator-space-before": "always",
|
||||
"selector-list-comma-newline-after": "always",
|
||||
"selector-list-comma-space-before": "never",
|
||||
"selector-max-empty-lines": 0,
|
||||
"selector-nested-pattern": "^(?!&-|&_).*",
|
||||
"selector-pseudo-class-case": "lower",
|
||||
"selector-pseudo-class-no-unknown": true,
|
||||
"selector-pseudo-class-parentheses-space-inside": "never",
|
||||
"selector-pseudo-element-case": "lower",
|
||||
"selector-pseudo-element-colon-notation": "single",
|
||||
"selector-pseudo-element-no-unknown": true,
|
||||
"selector-type-case": "lower",
|
||||
"shorthand-property-no-redundant-values": true,
|
||||
"string-no-newline": true,
|
||||
"string-quotes": "single",
|
||||
"unit-case": "lower",
|
||||
"unit-no-unknown": true,
|
||||
"value-list-comma-newline-after": "always-multi-line",
|
||||
"value-list-comma-space-after": "always-single-line",
|
||||
"value-list-comma-space-before": "never",
|
||||
"value-list-max-empty-lines": 0,
|
||||
'scss/at-rule-no-unknown': true,
|
||||
'scss/dollar-variable-colon-space-after': 'always',
|
||||
'scss/dollar-variable-colon-space-before': 'never',
|
||||
'scss/operator-no-newline-after': true,
|
||||
'scss/operator-no-newline-before': true,
|
||||
'scss/operator-no-unspaced': true,
|
||||
'scss/selector-no-redundant-nesting-selector': true,
|
||||
'selector-attribute-brackets-space-inside': 'never',
|
||||
'selector-attribute-operator-space-after': 'never',
|
||||
'selector-attribute-operator-space-before': 'never',
|
||||
'selector-combinator-space-after': 'always',
|
||||
'selector-combinator-space-before': 'always',
|
||||
'selector-list-comma-newline-after': 'always',
|
||||
'selector-list-comma-space-before': 'never',
|
||||
'selector-max-empty-lines': 0,
|
||||
'selector-nested-pattern': '^(?!&-|&_).*',
|
||||
'selector-pseudo-class-case': 'lower',
|
||||
'selector-pseudo-class-no-unknown': true,
|
||||
'selector-pseudo-class-parentheses-space-inside': 'never',
|
||||
'selector-pseudo-element-case': 'lower',
|
||||
'selector-pseudo-element-colon-notation': 'single',
|
||||
'selector-pseudo-element-no-unknown': true,
|
||||
'selector-type-case': 'lower',
|
||||
'shorthand-property-no-redundant-values': true,
|
||||
'string-no-newline': true,
|
||||
'string-quotes': 'single',
|
||||
'unit-case': 'lower',
|
||||
'unit-no-unknown': true,
|
||||
'value-list-comma-newline-after': 'always-multi-line',
|
||||
'value-list-comma-space-after': 'always-single-line',
|
||||
'value-list-comma-space-before': 'never',
|
||||
'value-list-max-empty-lines': 0,
|
||||
},
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
# MailPoet
|
||||
|
||||
The **MailPoet** plugin.
|
||||
|
||||
To use the official Docker-based development environment, see details
|
||||
@ -16,11 +17,13 @@ below.
|
||||
## Setup
|
||||
|
||||
### Requirements
|
||||
|
||||
- PHP >= 7.3 (only for the development environment, to run the plugin PHP >= 7.2 is required)
|
||||
- NodeJS
|
||||
- WordPress
|
||||
|
||||
### Installation
|
||||
|
||||
The instructions below assume you already have a working WordPress development environment:
|
||||
|
||||
```bash
|
||||
@ -171,8 +174,7 @@ _x('text to translate', 'context for translators', 'mailpoet');
|
||||
**in Twig views**
|
||||
|
||||
```html
|
||||
<%= __('text to translate') %>
|
||||
<%= _n('single text', 'plural text', $number) %>
|
||||
<%= __('text to translate') %> <%= _n('single text', 'plural text', $number) %>
|
||||
<%= _x('text to translate', 'context for translators') %>
|
||||
```
|
||||
|
||||
@ -183,12 +185,8 @@ The domain `mailpoet` will be added automatically by the Twig functions.
|
||||
First add the string to the translations block in the Twig view:
|
||||
|
||||
```html
|
||||
<% block translations %>
|
||||
<%= localize({
|
||||
'key': __('string to translate'),
|
||||
...
|
||||
}) %>
|
||||
<% endblock %>
|
||||
<% block translations %> <%= localize({ 'key': __('string to translate'), ... })
|
||||
%> <% endblock %>
|
||||
```
|
||||
|
||||
Then use `MailPoet.I18n.t('key')` to get the translated string on your Javascript code.
|
||||
@ -197,9 +195,11 @@ Then use `MailPoet.I18n.t('key')` to get the translated string on your Javascrip
|
||||
|
||||
To run the whole acceptance testing suite you need the docker daemon to be running and after that use a command: `./do test:acceptance`.
|
||||
If you want to run only a single test use the parameter `--file`:
|
||||
|
||||
```bash
|
||||
./do test:acceptance --skip-deps --file tests/acceptance/ReceiveStandardEmailCest.php
|
||||
```
|
||||
|
||||
The argument `--skip-deps` is useful locally to speed up the run.
|
||||
|
||||
If there are some unexpected errors you can delete all the runtime and start again.
|
||||
|
File diff suppressed because one or more lines are too long
@ -2,7 +2,5 @@ import jQuery from 'jquery';
|
||||
|
||||
jQuery(function adminDomReady($) {
|
||||
// dom ready
|
||||
$(function domReady() {
|
||||
|
||||
});
|
||||
$(function domReady() {});
|
||||
});
|
||||
|
@ -15,9 +15,11 @@ export type ErrorResponse = {
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isErrorResponse = (error: any): error is ErrorResponse => (
|
||||
error && typeof error === 'object' && 'errors' in error && Array.isArray(error.errors)
|
||||
);
|
||||
export const isErrorResponse = (error: any): error is ErrorResponse =>
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
'errors' in error &&
|
||||
Array.isArray(error.errors);
|
||||
|
||||
type ResponseType = JQuery.Deferred<Response, ErrorResponse>;
|
||||
|
||||
@ -94,7 +96,9 @@ export const MailPoetAjax = {
|
||||
},
|
||||
constructGetUrl: function constructGetUrl(options): string {
|
||||
this.init(options);
|
||||
return `${this.options.url as string}?${jQuery.param(this.getParams() as object)}`;
|
||||
return `${this.options.url as string}?${jQuery.param(
|
||||
this.getParams() as object,
|
||||
)}`;
|
||||
},
|
||||
request: function request(method, options): ResponseType {
|
||||
// set options
|
||||
@ -117,15 +121,26 @@ export const MailPoetAjax = {
|
||||
success: null,
|
||||
dataType: 'json',
|
||||
timeout: this.options.timeout,
|
||||
}).then((data: Response) => deferred.resolve(data), (failedXhr, textStatus) => {
|
||||
}).then(
|
||||
(data: Response) => deferred.resolve(data),
|
||||
(failedXhr, textStatus) => {
|
||||
let errorData: ErrorResponse;
|
||||
if (textStatus === 'timeout') {
|
||||
errorData = buildErrorResponse(MailPoetI18n.t('ajaxTimeoutErrorMessage').replace('%d', timeout.toString()));
|
||||
errorData = buildErrorResponse(
|
||||
MailPoetI18n.t('ajaxTimeoutErrorMessage').replace(
|
||||
'%d',
|
||||
timeout.toString(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
errorData = requestFailed(MailPoetI18n.t('ajaxFailedErrorMessage'), failedXhr);
|
||||
errorData = requestFailed(
|
||||
MailPoetI18n.t('ajaxFailedErrorMessage'),
|
||||
failedXhr,
|
||||
);
|
||||
}
|
||||
void deferred.reject(errorData);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// clear options
|
||||
this.options = {};
|
||||
|
@ -39,7 +39,10 @@ function track(name, data = []) {
|
||||
function exportMixpanel() {
|
||||
window.MailPoet.forceTrackEvent = track;
|
||||
|
||||
if (window.mailpoet_analytics_enabled && window.MailPoet.libs3rdPartyEnabled) {
|
||||
if (
|
||||
window.mailpoet_analytics_enabled &&
|
||||
window.MailPoet.libs3rdPartyEnabled
|
||||
) {
|
||||
window.MailPoet.trackEvent = track;
|
||||
} else {
|
||||
window.MailPoet.trackEvent = function emptyFunction() {};
|
||||
|
@ -30,7 +30,10 @@ export const withFeatureAnnouncement = <P extends Record<string, unknown>>(
|
||||
let beamerCallback;
|
||||
|
||||
function showPluginUpdateNotice() {
|
||||
if (!window.mailpoet_update_available || document.getElementById('mailpoet_update_notice')) {
|
||||
if (
|
||||
!window.mailpoet_update_available ||
|
||||
document.getElementById('mailpoet_update_notice')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const updateMailPoetNotice = ReactStringReplace(
|
||||
@ -99,7 +102,7 @@ export const withFeatureAnnouncement = <P extends Record<string, unknown>>(
|
||||
}: Omit<P, 'hasNews' | 'onBeamerClick'>) {
|
||||
return (
|
||||
<Component
|
||||
{...props as P}
|
||||
{...(props as P)}
|
||||
onBeamerClick={(e) => showBeamer(e)}
|
||||
hasNews={showDot}
|
||||
/>
|
||||
|
@ -3,9 +3,10 @@ import { api } from './config';
|
||||
|
||||
const API_URL = `${api.root}/mailpoet/v1/automation`;
|
||||
|
||||
export const request = (path: string, init?: RequestInit): ReturnType<typeof fetch> => (
|
||||
fetch(`${API_URL}/${path}`, init)
|
||||
);
|
||||
export const request = (
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): ReturnType<typeof fetch> => fetch(`${API_URL}/${path}`, init);
|
||||
|
||||
type Error<T> = {
|
||||
response?: Response;
|
||||
@ -18,26 +19,25 @@ type State<T> = {
|
||||
error?: Error<T>;
|
||||
};
|
||||
|
||||
type Result<T> = [
|
||||
(init?: RequestInit) => Promise<void>,
|
||||
State<T>,
|
||||
];
|
||||
type Result<T> = [(init?: RequestInit) => Promise<void>, State<T>];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Data = Record<string, any>;
|
||||
|
||||
export const useMutation = <T extends Data>(path: string, config?: RequestInit): Result<T> => {
|
||||
export const useMutation = <T extends Data>(
|
||||
path: string,
|
||||
config?: RequestInit,
|
||||
): Result<T> => {
|
||||
const [state, setState] = useState<State<T>>({
|
||||
data: undefined,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const mutation = useCallback(async (init?: RequestInit) => {
|
||||
const mutation = useCallback(
|
||||
async (init?: RequestInit) => {
|
||||
setState((prevState) => ({ ...prevState, loading: true }));
|
||||
const response = await request(
|
||||
path,
|
||||
{
|
||||
const response = await request(path, {
|
||||
...config,
|
||||
...init,
|
||||
headers: {
|
||||
@ -45,8 +45,7 @@ export const useMutation = <T extends Data>(path: string, config?: RequestInit):
|
||||
...(init?.headers ?? {}),
|
||||
'x-wp-nonce': api.nonce,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await response.json();
|
||||
@ -58,19 +57,24 @@ export const useMutation = <T extends Data>(path: string, config?: RequestInit):
|
||||
} finally {
|
||||
setState((prevState) => ({ ...prevState, loading: false }));
|
||||
}
|
||||
}, [config, path]);
|
||||
},
|
||||
[config, path],
|
||||
);
|
||||
|
||||
return [mutation, state];
|
||||
};
|
||||
|
||||
export const useQuery = <T extends Data>(path: string, init?: RequestInit): State<T> => {
|
||||
export const useQuery = <T extends Data>(
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): State<T> => {
|
||||
const [mutation, result] = useMutation<T>(path, init);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
void mutation();
|
||||
},
|
||||
[], /* eslint-disable-line react-hooks/exhaustive-deps -- request only on initial load */
|
||||
[] /* eslint-disable-line react-hooks/exhaustive-deps -- request only on initial load */,
|
||||
);
|
||||
|
||||
return result;
|
||||
|
@ -13,24 +13,26 @@ function ApiCheck(): JSX.Element {
|
||||
}
|
||||
|
||||
function RecreateSchemaButton(): JSX.Element {
|
||||
const [createSchema, { loading, error }] = useMutation('system/database', { method: 'POST' });
|
||||
const [createSchema, { loading, error }] = useMutation('system/database', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createSchema()}
|
||||
disabled={loading}
|
||||
>
|
||||
<button type="button" onClick={() => createSchema()} disabled={loading}>
|
||||
Recreate DB schema (data will be lost)
|
||||
</button>
|
||||
{error && (<div>{error?.data?.message ?? 'An unknown error occurred'}</div>)}
|
||||
{error && (
|
||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteSchemaButton(): JSX.Element {
|
||||
const [deleteSchema, { loading, error }] = useMutation('system/database', { method: 'DELETE' });
|
||||
const [deleteSchema, { loading, error }] = useMutation('system/database', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -38,13 +40,16 @@ function DeleteSchemaButton(): JSX.Element {
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
await deleteSchema();
|
||||
window.location.href = '/wp-admin/admin.php?page=mailpoet-experimental';
|
||||
window.location.href =
|
||||
'/wp-admin/admin.php?page=mailpoet-experimental';
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
Delete DB schema & deactivate feature
|
||||
</button>
|
||||
{error && (<div>{error?.data?.message ?? 'An unknown error occurred'}</div>)}
|
||||
{error && (
|
||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -30,20 +30,26 @@ const createWorkflow = () => {
|
||||
};
|
||||
|
||||
export function CreateTestingWorkflowButton(): JSX.Element {
|
||||
const [createSchema, { loading, error }] = useMutation('workflows', { method: 'POST' });
|
||||
const [createSchema, { loading, error }] = useMutation('workflows', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createSchema({
|
||||
onClick={() =>
|
||||
createSchema({
|
||||
body: JSON.stringify(createWorkflow()),
|
||||
})}
|
||||
})
|
||||
}
|
||||
disabled={loading}
|
||||
>
|
||||
Create testing workflow
|
||||
</button>
|
||||
{error && (<div>{error?.data?.message ?? 'An unknown error occurred'}</div>)}
|
||||
{error && (
|
||||
<div>{error?.data?.message ?? 'An unknown error occurred'}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -12,10 +12,7 @@ export function WithoutIcons() {
|
||||
<>
|
||||
<Heading level={3}>Small buttons</Heading>
|
||||
<p>
|
||||
<Button
|
||||
onClick={action('primary small')}
|
||||
dimension="small"
|
||||
>
|
||||
<Button onClick={action('primary small')} dimension="small">
|
||||
Primary button
|
||||
</Button>
|
||||
<Button
|
||||
@ -44,27 +41,14 @@ export function WithoutIcons() {
|
||||
|
||||
<Heading level={3}>Regular buttons</Heading>
|
||||
<p>
|
||||
<Button
|
||||
onClick={action('primary regular')}
|
||||
>
|
||||
Primary button
|
||||
</Button>
|
||||
<Button
|
||||
onClick={action('secondary regular')}
|
||||
variant="secondary"
|
||||
>
|
||||
<Button onClick={action('primary regular')}>Primary button</Button>
|
||||
<Button onClick={action('secondary regular')} variant="secondary">
|
||||
Secondary button
|
||||
</Button>
|
||||
<Button
|
||||
onClick={action('tertiary regular')}
|
||||
variant="tertiary"
|
||||
>
|
||||
<Button onClick={action('tertiary regular')} variant="tertiary">
|
||||
Tertiary button
|
||||
</Button>
|
||||
<Button
|
||||
onClick={action('destructive regular')}
|
||||
variant="destructive"
|
||||
>
|
||||
<Button onClick={action('destructive regular')} variant="destructive">
|
||||
Destructive button
|
||||
</Button>
|
||||
</p>
|
||||
@ -72,25 +56,14 @@ export function WithoutIcons() {
|
||||
|
||||
<Heading level={3}>Disabled buttons</Heading>
|
||||
<p>
|
||||
<Button isDisabled>
|
||||
Primary button
|
||||
</Button>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="secondary"
|
||||
>
|
||||
<Button isDisabled>Primary button</Button>
|
||||
<Button isDisabled variant="secondary">
|
||||
Secondary button
|
||||
</Button>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="tertiary"
|
||||
>
|
||||
<Button isDisabled variant="tertiary">
|
||||
Tertiary button
|
||||
</Button>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="destructive"
|
||||
>
|
||||
<Button isDisabled variant="destructive">
|
||||
Destructive button
|
||||
</Button>
|
||||
</p>
|
||||
@ -98,25 +71,14 @@ export function WithoutIcons() {
|
||||
|
||||
<Heading level={3}>Buttons with spinner</Heading>
|
||||
<p>
|
||||
<Button withSpinner>
|
||||
Primary button
|
||||
</Button>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="secondary"
|
||||
>
|
||||
<Button withSpinner>Primary button</Button>
|
||||
<Button withSpinner variant="secondary">
|
||||
Secondary button
|
||||
</Button>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="tertiary"
|
||||
>
|
||||
<Button withSpinner variant="tertiary">
|
||||
Tertiary button
|
||||
</Button>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="destructive"
|
||||
>
|
||||
<Button withSpinner variant="destructive">
|
||||
Destructive button
|
||||
</Button>
|
||||
</p>
|
||||
@ -124,10 +86,7 @@ export function WithoutIcons() {
|
||||
|
||||
<Heading level={3}>Full width buttons</Heading>
|
||||
<p>
|
||||
<Button
|
||||
onClick={action('primary full-width')}
|
||||
isFullWidth
|
||||
>
|
||||
<Button onClick={action('primary full-width')} isFullWidth>
|
||||
Primary button
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -54,10 +54,7 @@ export function WithIcons() {
|
||||
|
||||
<Heading level={3}>Regular buttons</Heading>
|
||||
<p>
|
||||
<Button
|
||||
onClick={action('icon start primary regular')}
|
||||
iconStart={icon}
|
||||
>
|
||||
<Button onClick={action('icon start primary regular')} iconStart={icon}>
|
||||
Icon start
|
||||
</Button>
|
||||
<Button
|
||||
@ -90,35 +87,15 @@ export function WithIcons() {
|
||||
|
||||
<Heading level={3}>Disabled buttons</Heading>
|
||||
<p>
|
||||
<Button
|
||||
isDisabled
|
||||
iconStart={icon}
|
||||
>
|
||||
<Button isDisabled iconStart={icon}>
|
||||
Icon start
|
||||
</Button>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="secondary"
|
||||
iconStart={icon}
|
||||
iconEnd={icon}
|
||||
>
|
||||
<Button isDisabled variant="secondary" iconStart={icon} iconEnd={icon}>
|
||||
Both icons
|
||||
</Button>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="secondary"
|
||||
iconStart={icon}
|
||||
/>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="tertiary"
|
||||
iconStart={icon}
|
||||
/>
|
||||
<Button
|
||||
isDisabled
|
||||
variant="destructive"
|
||||
iconEnd={icon}
|
||||
>
|
||||
<Button isDisabled variant="secondary" iconStart={icon} />
|
||||
<Button isDisabled variant="tertiary" iconStart={icon} />
|
||||
<Button isDisabled variant="destructive" iconEnd={icon}>
|
||||
Icon end
|
||||
</Button>
|
||||
</p>
|
||||
@ -126,35 +103,15 @@ export function WithIcons() {
|
||||
|
||||
<Heading level={3}>Buttons with spinner</Heading>
|
||||
<p>
|
||||
<Button
|
||||
withSpinner
|
||||
iconStart={icon}
|
||||
>
|
||||
<Button withSpinner iconStart={icon}>
|
||||
Icon start
|
||||
</Button>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="secondary"
|
||||
iconStart={icon}
|
||||
iconEnd={icon}
|
||||
>
|
||||
<Button withSpinner variant="secondary" iconStart={icon} iconEnd={icon}>
|
||||
Both icons
|
||||
</Button>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="secondary"
|
||||
iconStart={icon}
|
||||
/>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="tertiary"
|
||||
iconStart={icon}
|
||||
/>
|
||||
<Button
|
||||
withSpinner
|
||||
variant="destructive"
|
||||
iconEnd={icon}
|
||||
>
|
||||
<Button withSpinner variant="secondary" iconStart={icon} />
|
||||
<Button withSpinner variant="tertiary" iconStart={icon} />
|
||||
<Button withSpinner variant="destructive" iconEnd={icon}>
|
||||
Icon end
|
||||
</Button>
|
||||
</p>
|
||||
|
@ -45,12 +45,7 @@ function Button({
|
||||
target={target}
|
||||
rel={rel}
|
||||
disabled={isDisabled}
|
||||
className={
|
||||
classnames(
|
||||
className,
|
||||
'button',
|
||||
'mailpoet-button',
|
||||
{
|
||||
className={classnames(className, 'button', 'mailpoet-button', {
|
||||
'mailpoet-button-with-spinner': withSpinner,
|
||||
'mailpoet-button-disabled': isDisabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
@ -59,9 +54,7 @@ function Button({
|
||||
'button-link': variant === 'tertiary',
|
||||
'button-link button-link-delete': variant === 'destructive',
|
||||
'button-small': dimension === 'small',
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
{iconStart}
|
||||
|
@ -17,9 +17,21 @@ export function CategoriesWithCount() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Categories onSelect={noop} categories={categories} active={categories[0].name} />
|
||||
<Categories onSelect={noop} categories={categories} active={categories[2].name} />
|
||||
<Categories onSelect={noop} categories={categories} active={categories[4].name} />
|
||||
<Categories
|
||||
onSelect={noop}
|
||||
categories={categories}
|
||||
active={categories[0].name}
|
||||
/>
|
||||
<Categories
|
||||
onSelect={noop}
|
||||
categories={categories}
|
||||
active={categories[2].name}
|
||||
/>
|
||||
<Categories
|
||||
onSelect={noop}
|
||||
categories={categories}
|
||||
active={categories[4].name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -35,9 +47,21 @@ export function CategoriesWithoutCount() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Categories onSelect={noop} categories={categories} active={categories[0].name} />
|
||||
<Categories onSelect={noop} categories={categories} active={categories[2].name} />
|
||||
<Categories onSelect={noop} categories={categories} active={categories[4].name} />
|
||||
<Categories
|
||||
onSelect={noop}
|
||||
categories={categories}
|
||||
active={categories[0].name}
|
||||
/>
|
||||
<Categories
|
||||
onSelect={noop}
|
||||
categories={categories}
|
||||
active={categories[2].name}
|
||||
/>
|
||||
<Categories
|
||||
onSelect={noop}
|
||||
categories={categories}
|
||||
active={categories[4].name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -16,11 +16,7 @@ function Categories({ onSelect, categories, active }: Props) {
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="mailpoet-categories">
|
||||
{ cats }
|
||||
</div>
|
||||
);
|
||||
return <div className="mailpoet-categories">{cats}</div>;
|
||||
}
|
||||
|
||||
export default Categories;
|
||||
|
@ -20,10 +20,7 @@ function CategoriesItem({
|
||||
automationId,
|
||||
active,
|
||||
}: Props) {
|
||||
const classes = classNames(
|
||||
'mailpoet-categories-item',
|
||||
{ active: !!active },
|
||||
);
|
||||
const classes = classNames('mailpoet-categories-item', { active: !!active });
|
||||
|
||||
return (
|
||||
<a
|
||||
@ -41,7 +38,7 @@ function CategoriesItem({
|
||||
</span>
|
||||
{count > 0 && (
|
||||
<span className="mailpoet-categories-count">
|
||||
{ parseInt(count.toString(), 10).toLocaleString() }
|
||||
{parseInt(count.toString(), 10).toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
|
@ -8,13 +8,21 @@ function ConfirmAlert(props) {
|
||||
template: ReactDOMServer.renderToString(
|
||||
<>
|
||||
<p>{props.message}</p>
|
||||
<button id="mailpoet_alert_cancel" className="button button-secondary" type="button">
|
||||
<button
|
||||
id="mailpoet_alert_cancel"
|
||||
className="button button-secondary"
|
||||
type="button"
|
||||
>
|
||||
{props.cancelLabel}
|
||||
</button>
|
||||
<button id="mailpoet_alert_confirm" className="button button-primary" type="submit">
|
||||
<button
|
||||
id="mailpoet_alert_confirm"
|
||||
className="button button-primary"
|
||||
type="submit"
|
||||
>
|
||||
{props.confirmLabel}
|
||||
</button>
|
||||
</>
|
||||
</>,
|
||||
),
|
||||
onInit: () => {
|
||||
document
|
||||
@ -55,6 +63,6 @@ export default function confirmAlert(props) {
|
||||
cancelLabel={props.cancelLabel}
|
||||
confirmLabel={props.confirmLabel}
|
||||
onConfirm={props.onConfirm}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ export default async function callApi({ endpoint, action, data }) {
|
||||
});
|
||||
return { success: true, res };
|
||||
} catch (res) {
|
||||
const error = isErrorResponse(res) ? res.errors.map((e) => e.message) : null;
|
||||
const error = isErrorResponse(res)
|
||||
? res.errors.map((e) => e.message)
|
||||
: null;
|
||||
return { success: false, error, res };
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,19 @@
|
||||
import MailPoet from 'mailpoet';
|
||||
|
||||
const sleep = (ms:number) => new Promise((resolve) => { setTimeout(resolve, ms); });
|
||||
const sleep = (ms: number) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
|
||||
export default async function trackEvent(
|
||||
{
|
||||
export default async function trackEvent({
|
||||
name,
|
||||
data,
|
||||
timeout = 0,
|
||||
}:{ name:string, data:object, timeout:number },
|
||||
) {
|
||||
}: {
|
||||
name: string;
|
||||
data: object;
|
||||
timeout: number;
|
||||
}) {
|
||||
MailPoet.trackEvent(name, data);
|
||||
return sleep(timeout);
|
||||
}
|
||||
|
@ -9,21 +9,13 @@ export default {
|
||||
component: Datepicker,
|
||||
};
|
||||
|
||||
function DatepickerWrapper({
|
||||
...props
|
||||
}) {
|
||||
function DatepickerWrapper({ ...props }) {
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const onChange = (date:Date) => {
|
||||
const onChange = (date: Date) => {
|
||||
props.onChange(date);
|
||||
setStartDate(date);
|
||||
};
|
||||
return (
|
||||
<Datepicker
|
||||
{...props}
|
||||
selected={startDate}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
return <Datepicker {...props} selected={startDate} onChange={onChange} />;
|
||||
}
|
||||
|
||||
export function Datepickers() {
|
||||
|
@ -18,16 +18,11 @@ function Datepicker({
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
'mailpoet-datepicker mailpoet-form-input',
|
||||
{
|
||||
className={classnames('mailpoet-datepicker mailpoet-form-input', {
|
||||
[`mailpoet-form-input-${dimension}`]: dimension,
|
||||
'mailpoet-disabled': props.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
>
|
||||
{iconStart}
|
||||
<ReactDatePicker
|
||||
|
@ -17,13 +17,11 @@ function Checkbox({
|
||||
}: Props) {
|
||||
return (
|
||||
<label
|
||||
className={
|
||||
classnames({
|
||||
className={classnames({
|
||||
'mailpoet-form-checkbox': true,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
})
|
||||
}
|
||||
})}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
<input
|
||||
|
@ -26,7 +26,7 @@ function CheckboxGroup({
|
||||
|
||||
const handleChange = (value: CheckboxValueType, isChecked: boolean) => {
|
||||
const index = values.indexOf(value);
|
||||
let newValues:CheckboxValueType[] = [];
|
||||
let newValues: CheckboxValueType[] = [];
|
||||
if (isChecked && index === -1) {
|
||||
newValues = values.concat([value]);
|
||||
}
|
||||
@ -42,7 +42,7 @@ function CheckboxGroup({
|
||||
<div>
|
||||
{options.map((props: CheckboxProps) => {
|
||||
const { label, ...attributes } = props;
|
||||
const value = (props.value as CheckboxValueType);
|
||||
const value = props.value as CheckboxValueType;
|
||||
return (
|
||||
<Checkbox
|
||||
checked={values.includes(value)}
|
||||
|
@ -12,11 +12,7 @@ export function Inputs() {
|
||||
<>
|
||||
<Heading level={3}>Small inputs</Heading>
|
||||
<div>
|
||||
<Input
|
||||
type="text"
|
||||
dimension="small"
|
||||
placeholder="Small input value"
|
||||
/>
|
||||
<Input type="text" dimension="small" placeholder="Small input value" />
|
||||
<div className="mailpoet-gap" />
|
||||
<Input
|
||||
type="text"
|
||||
@ -43,10 +39,7 @@ export function Inputs() {
|
||||
<br />
|
||||
<Heading level={3}>Regular inputs</Heading>
|
||||
<div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Regular input"
|
||||
/>
|
||||
<Input type="text" placeholder="Regular input" />
|
||||
<div className="mailpoet-gap" />
|
||||
<Input
|
||||
type="text"
|
||||
@ -67,20 +60,12 @@ export function Inputs() {
|
||||
iconEnd={icon}
|
||||
/>
|
||||
<div className="mailpoet-gap" />
|
||||
<Input
|
||||
disabled
|
||||
type="text"
|
||||
placeholder="Disabled input"
|
||||
/>
|
||||
<Input disabled type="text" placeholder="Disabled input" />
|
||||
</div>
|
||||
<br />
|
||||
<Heading level={3}>Full-width inputs</Heading>
|
||||
<div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Full-width input"
|
||||
isFullWidth
|
||||
/>
|
||||
<Input type="text" placeholder="Full-width input" isFullWidth />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Full-width input with iconStart"
|
||||
|
@ -23,25 +23,25 @@ function Input({
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
className,
|
||||
'mailpoet-form-input',
|
||||
{
|
||||
className={classnames(className, 'mailpoet-form-input', {
|
||||
[`mailpoet-form-input-${dimension}`]: dimension,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
>
|
||||
{iconStart}
|
||||
<input {...attributes} />
|
||||
{customLabel && <div className="mailpoet-form-input-label">{customLabel}</div>}
|
||||
{customLabel && (
|
||||
<div className="mailpoet-form-input-label">{customLabel}</div>
|
||||
)}
|
||||
{tooltip && (
|
||||
<>
|
||||
<span className="mailpoet-form-tooltip-holder">
|
||||
<span className="mailpoet-form-tooltip-icon" data-tip data-for={attributes.name} />
|
||||
<span
|
||||
className="mailpoet-form-tooltip-icon"
|
||||
data-tip
|
||||
data-for={attributes.name}
|
||||
/>
|
||||
</span>
|
||||
<Tooltip place="right" multiline id={attributes.name}>
|
||||
{tooltip}
|
||||
|
@ -23,18 +23,10 @@ export function Radios() {
|
||||
<>
|
||||
<Heading level={3}>Inline individual radios</Heading>
|
||||
<div>
|
||||
<Radio
|
||||
onCheck={action('radio-individual-1')}
|
||||
name="story"
|
||||
value="1"
|
||||
>
|
||||
<Radio onCheck={action('radio-individual-1')} name="story" value="1">
|
||||
Option 1
|
||||
</Radio>
|
||||
<Radio
|
||||
onCheck={action('radio-individual-2')}
|
||||
name="story"
|
||||
value="2"
|
||||
>
|
||||
<Radio onCheck={action('radio-individual-2')} name="story" value="2">
|
||||
Option 2
|
||||
</Radio>
|
||||
</div>
|
||||
|
@ -33,7 +33,7 @@ function RadioGroup({
|
||||
<div>
|
||||
{options.map((props: RadioProps) => {
|
||||
const { label, ...attributes } = props;
|
||||
const value = (props.value as RadioValueType);
|
||||
const value = props.value as RadioValueType;
|
||||
return (
|
||||
<Radio
|
||||
checked={currentValue === value}
|
||||
|
@ -17,13 +17,11 @@ function Radio({
|
||||
}: Props) {
|
||||
return (
|
||||
<label
|
||||
className={
|
||||
classnames({
|
||||
className={classnames({
|
||||
'mailpoet-form-radio': true,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
})
|
||||
}
|
||||
})}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
<input
|
||||
|
@ -26,7 +26,8 @@ export function ReactSelect() {
|
||||
},
|
||||
{
|
||||
value: 'long',
|
||||
label: 'Very very very very very very very very very very very very long option',
|
||||
label:
|
||||
'Very very very very very very very very very very very very long option',
|
||||
tag: 'long',
|
||||
count: 1234,
|
||||
},
|
||||
|
@ -19,9 +19,13 @@ type LabelRendererProps = {
|
||||
function LabelRenderer(data: LabelRendererProps) {
|
||||
return (
|
||||
<div className="mailpoet-form-react-select-option">
|
||||
{data.tag && <span className="mailpoet-form-react-select-tag">{data.tag}</span>}
|
||||
{data.tag && (
|
||||
<span className="mailpoet-form-react-select-tag">{data.tag}</span>
|
||||
)}
|
||||
<span>{data.label}</span>
|
||||
{data.count !== undefined && <span className="mailpoet-form-react-select-count">{data.count}</span>}
|
||||
{data.count !== undefined && (
|
||||
<span className="mailpoet-form-react-select-count">{data.count}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -43,14 +47,12 @@ function Option(props: OptionProps<OptionData>) {
|
||||
style={style}
|
||||
ref={props.innerRef}
|
||||
{...props.innerProps}
|
||||
className={
|
||||
classnames({
|
||||
className={classnames({
|
||||
'mailpoet-form-react-select__option': true,
|
||||
'mailpoet-form-react-select__option--is-disabled': props.isDisabled,
|
||||
'mailpoet-form-react-select__option--is-focused': props.isFocused,
|
||||
'mailpoet-form-react-select__option--is-selected': props.isSelected,
|
||||
})
|
||||
}
|
||||
})}
|
||||
>
|
||||
{LabelRenderer(props.data)}
|
||||
</div>
|
||||
@ -62,12 +64,11 @@ function SingleValue(props: any) {
|
||||
return (
|
||||
<div
|
||||
{...props.innerProps}
|
||||
className={
|
||||
classnames({
|
||||
className={classnames({
|
||||
'mailpoet-form-react-select__single-value': true,
|
||||
'mailpoet-form-react-select__single-value--is-disabled': props.isDisabled,
|
||||
})
|
||||
}
|
||||
'mailpoet-form-react-select__single-value--is-disabled':
|
||||
props.isDisabled,
|
||||
})}
|
||||
>
|
||||
{LabelRenderer(props.data as LabelRendererProps)}
|
||||
</div>
|
||||
@ -105,17 +106,11 @@ function ReactSelect({
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
'mailpoet-form-input',
|
||||
'mailpoet-form-select',
|
||||
{
|
||||
className={classnames('mailpoet-form-input', 'mailpoet-form-select', {
|
||||
[`mailpoet-form-input-${dimension}`]: dimension,
|
||||
'mailpoet-disabled': props.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
{iconStart}
|
||||
|
@ -18,10 +18,7 @@ export function NativeSelect() {
|
||||
<option value="3">Opt 3</option>
|
||||
</Select>
|
||||
<div className="mailpoet-gap" />
|
||||
<Select
|
||||
isMinWidth
|
||||
iconStart={icon}
|
||||
>
|
||||
<Select isMinWidth iconStart={icon}>
|
||||
<option value="1">Opt 1</option>
|
||||
<option value="2">Opt 2</option>
|
||||
<option value="3">Opt 3</option>
|
||||
@ -36,10 +33,7 @@ export function NativeSelect() {
|
||||
<option value="3">Option 3</option>
|
||||
</Select>
|
||||
<div className="mailpoet-gap" />
|
||||
<Select
|
||||
dimension="small"
|
||||
iconStart={icon}
|
||||
>
|
||||
<Select dimension="small" iconStart={icon}>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="3">Option 3</option>
|
||||
@ -73,10 +67,7 @@ export function NativeSelect() {
|
||||
<option value="3">Option 3</option>
|
||||
</Select>
|
||||
<div className="mailpoet-gap" />
|
||||
<Select
|
||||
isFullWidth
|
||||
iconStart={icon}
|
||||
>
|
||||
<Select isFullWidth iconStart={icon}>
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="3">Option 3</option>
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
forwardRef,
|
||||
ReactNode,
|
||||
Ref,
|
||||
SelectHTMLAttributes,
|
||||
} from 'react';
|
||||
import { forwardRef, ReactNode, Ref, SelectHTMLAttributes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
type Props = SelectHTMLAttributes<HTMLSelectElement> & {
|
||||
@ -15,7 +10,9 @@ type Props = SelectHTMLAttributes<HTMLSelectElement> & {
|
||||
automationId?: string;
|
||||
};
|
||||
|
||||
const Select = forwardRef(({
|
||||
const Select = forwardRef(
|
||||
(
|
||||
{
|
||||
children,
|
||||
dimension,
|
||||
isFullWidth,
|
||||
@ -23,26 +20,23 @@ const Select = forwardRef(({
|
||||
iconStart,
|
||||
automationId,
|
||||
...attributes
|
||||
}: Props, ref?: Ref<HTMLSelectElement>) => (
|
||||
}: Props,
|
||||
ref?: Ref<HTMLSelectElement>,
|
||||
) => (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
'mailpoet-form-input',
|
||||
'mailpoet-form-select',
|
||||
{
|
||||
className={classnames('mailpoet-form-input', 'mailpoet-form-select', {
|
||||
[`mailpoet-form-input-${dimension}`]: dimension,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
'mailpoet-min-width': isMinWidth,
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
>
|
||||
{iconStart}
|
||||
<select {...attributes} ref={ref} data-automation-id={automationId}>
|
||||
{children}
|
||||
</select>
|
||||
</div>
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
export default Select;
|
||||
|
@ -11,30 +11,19 @@ export function Textareas() {
|
||||
<>
|
||||
<Heading level={3}>Small textareas</Heading>
|
||||
<div>
|
||||
<Textarea
|
||||
dimension="small"
|
||||
placeholder="Small textarea value"
|
||||
/>
|
||||
<Textarea dimension="small" placeholder="Small textarea value" />
|
||||
</div>
|
||||
<br />
|
||||
<Heading level={3}>Regular textareas</Heading>
|
||||
<div>
|
||||
<Textarea
|
||||
placeholder="Regular textarea"
|
||||
/>
|
||||
<Textarea placeholder="Regular textarea" />
|
||||
<div className="mailpoet-gap" />
|
||||
<Textarea
|
||||
disabled
|
||||
placeholder="Disabled textarea"
|
||||
/>
|
||||
<Textarea disabled placeholder="Disabled textarea" />
|
||||
</div>
|
||||
<br />
|
||||
<Heading level={3}>Full-width textareas</Heading>
|
||||
<div>
|
||||
<Textarea
|
||||
placeholder="Full-width textarea"
|
||||
isFullWidth
|
||||
/>
|
||||
<Textarea placeholder="Full-width textarea" isFullWidth />
|
||||
</div>
|
||||
<br />
|
||||
</>
|
||||
|
@ -21,24 +21,24 @@ function Textarea({
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
className,
|
||||
'mailpoet-form-textarea',
|
||||
{
|
||||
className={classnames(className, 'mailpoet-form-textarea', {
|
||||
[`mailpoet-form-textarea-${dimension}`]: dimension,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
'mailpoet-full-width': isFullWidth,
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
>
|
||||
<textarea className={classnames({ code: isCode })} {...attributes} />
|
||||
{customLabel && <div className="mailpoet-form-input-label">{customLabel}</div>}
|
||||
{customLabel && (
|
||||
<div className="mailpoet-form-input-label">{customLabel}</div>
|
||||
)}
|
||||
{tooltip && (
|
||||
<>
|
||||
<span className="mailpoet-form-tooltip-holder">
|
||||
<span className="mailpoet-form-tooltip-icon" data-tip data-for={attributes.name} />
|
||||
<span
|
||||
className="mailpoet-form-tooltip-icon"
|
||||
data-tip
|
||||
data-for={attributes.name}
|
||||
/>
|
||||
</span>
|
||||
<Tooltip place="right" multiline id={attributes.name}>
|
||||
{tooltip}
|
||||
|
@ -15,11 +15,7 @@ export function Toggles() {
|
||||
<Grid.Column dimension="small">
|
||||
<Grid.SpaceBetween>
|
||||
<label htmlFor="toggle-1">Toggle regular</label>
|
||||
<Toggle
|
||||
onCheck={action('toggle-1')}
|
||||
id="toggle-1"
|
||||
name="toggle-1"
|
||||
/>
|
||||
<Toggle onCheck={action('toggle-1')} id="toggle-1" name="toggle-1" />
|
||||
</Grid.SpaceBetween>
|
||||
<div className="mailpoet-gap" />
|
||||
<Grid.SpaceBetween>
|
||||
|
@ -16,14 +16,12 @@ function Toggle({
|
||||
}: Props) {
|
||||
return (
|
||||
<label
|
||||
className={
|
||||
classnames({
|
||||
className={classnames({
|
||||
[className]: className,
|
||||
'mailpoet-form-toggle': true,
|
||||
[`mailpoet-form-toggle-${dimension}`]: dimension,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
})
|
||||
}
|
||||
})}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
<input
|
||||
|
@ -14,35 +14,18 @@ export function YesNos() {
|
||||
<Heading level={3}>YesNos</Heading>
|
||||
<Grid.Column dimension="small">
|
||||
<Grid.SpaceBetween verticalAlign="center">
|
||||
<div>
|
||||
YesNo
|
||||
</div>
|
||||
<YesNo
|
||||
onCheck={action('yesno-1')}
|
||||
name="yesno-1"
|
||||
/>
|
||||
<div>YesNo</div>
|
||||
<YesNo onCheck={action('yesno-1')} name="yesno-1" />
|
||||
</Grid.SpaceBetween>
|
||||
<div className="mailpoet-gap" />
|
||||
<Grid.SpaceBetween verticalAlign="center">
|
||||
<div>
|
||||
YesNo with error
|
||||
</div>
|
||||
<YesNo
|
||||
showError
|
||||
onCheck={action('yesno-2')}
|
||||
name="yesno-2"
|
||||
/>
|
||||
<div>YesNo with error</div>
|
||||
<YesNo showError onCheck={action('yesno-2')} name="yesno-2" />
|
||||
</Grid.SpaceBetween>
|
||||
<div className="mailpoet-gap" />
|
||||
<Grid.SpaceBetween verticalAlign="center">
|
||||
<div>
|
||||
YesNo disabled
|
||||
</div>
|
||||
<YesNo
|
||||
disabled
|
||||
onCheck={action('yesno-3')}
|
||||
name="yesno-3"
|
||||
/>
|
||||
<div>YesNo disabled</div>
|
||||
<YesNo disabled onCheck={action('yesno-3')} name="yesno-3" />
|
||||
</Grid.SpaceBetween>
|
||||
</Grid.Column>
|
||||
</>
|
||||
|
@ -1,5 +1,8 @@
|
||||
export default (
|
||||
<svg width="17" height="16" viewBox="0 0 17 16">
|
||||
<path fill="currentColor" d="M12.407 3.31c.576.576.576 1.509 0 2.084L9.914 7.888l2.411 2.412c.586.585.586 1.535 0 2.121-.585.586-1.535.586-2.12 0l-2.413-2.412L5.3 12.503c-.576.575-1.509.575-2.084 0-.575-.575-.575-1.508 0-2.083l2.493-2.495-2.41-2.41c-.587-.587-.587-1.536 0-2.122.585-.586 1.535-.586 2.12 0L7.83 5.804l2.494-2.493c.575-.576 1.508-.576 2.083 0z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.407 3.31c.576.576.576 1.509 0 2.084L9.914 7.888l2.411 2.412c.586.585.586 1.535 0 2.121-.585.586-1.535.586-2.12 0l-2.413-2.412L5.3 12.503c-.576.575-1.509.575-2.084 0-.575-.575-.575-1.508 0-2.083l2.493-2.495-2.41-2.41c-.587-.587-.587-1.536 0-2.122.585-.586 1.535-.586 2.12 0L7.83 5.804l2.494-2.493c.575-.576 1.508-.576 2.083 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -1,5 +1,8 @@
|
||||
export default (
|
||||
<svg width="13" height="11" viewBox="0 0 13 11">
|
||||
<path fill="currentColor" d="M9.967.638c.483-.698 1.405-.846 2.06-.33.654.515.793 1.499.31 2.197l-5.44 7.857c-.55.794-1.64.857-2.267.132l-3.4-3.928c-.552-.638-.515-1.632.083-2.22.598-.59 1.53-.55 2.082.088l2.19 2.532L9.968.638z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M9.967.638c.483-.698 1.405-.846 2.06-.33.654.515.793 1.499.31 2.197l-5.44 7.857c-.55.794-1.64.857-2.267.132l-3.4-3.928c-.552-.638-.515-1.632.083-2.22.598-.59 1.53-.55 2.082.088l2.19 2.532L9.968.638z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
@ -20,13 +20,11 @@ function YesNo({
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames({
|
||||
className={classnames({
|
||||
'mailpoet-form-yesno': true,
|
||||
'mailpoet-form-yesno-error': showError,
|
||||
'mailpoet-disabled': attributes.disabled,
|
||||
})
|
||||
}
|
||||
})}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
<label>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { curry } from 'lodash';
|
||||
|
||||
const setLowercaseValue = curry((setter: (value: string) => void, value: string) => {
|
||||
const setLowercaseValue = curry(
|
||||
(setter: (value: string) => void, value: string) => {
|
||||
setter(value.toLowerCase());
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default setLowercaseValue;
|
||||
|
@ -8,27 +8,22 @@ export default {
|
||||
};
|
||||
|
||||
export function Layouts(): ReactElement {
|
||||
const content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi libero sapien, tristique sollicitudin lobortis id, viverra id libero.';
|
||||
const content =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi libero sapien, tristique sollicitudin lobortis id, viverra id libero.';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading level={3}>Column</Heading>
|
||||
<Grid.Column className="custom-class">
|
||||
{content}
|
||||
</Grid.Column>
|
||||
<Grid.Column className="custom-class">{content}</Grid.Column>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
<Grid.Column align="center">
|
||||
{content}
|
||||
</Grid.Column>
|
||||
<Grid.Column align="center">{content}</Grid.Column>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
<Heading level={3}>Column - small</Heading>
|
||||
<Grid.Column dimension="small">
|
||||
{content}
|
||||
</Grid.Column>
|
||||
<Grid.Column dimension="small">{content}</Grid.Column>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
@ -77,9 +72,7 @@ export function Layouts(): ReactElement {
|
||||
<br />
|
||||
Part
|
||||
</div>
|
||||
<div>
|
||||
Right Part
|
||||
</div>
|
||||
<div>Right Part</div>
|
||||
</Grid.SpaceBetween>
|
||||
</Grid.Column>
|
||||
|
||||
@ -93,22 +86,16 @@ export function Layouts(): ReactElement {
|
||||
<br />
|
||||
Part
|
||||
</div>
|
||||
<div>
|
||||
Right Part
|
||||
</div>
|
||||
<div>Right Part</div>
|
||||
</Grid.SpaceBetween>
|
||||
</Grid.Column>
|
||||
|
||||
<Heading level={3}>Centered row</Heading>
|
||||
<Grid.Column>
|
||||
<Grid.CenteredRow className="custom-class">
|
||||
<div>
|
||||
Left
|
||||
</div>
|
||||
<div>Left</div>
|
||||
<Input type="text" />
|
||||
<div>
|
||||
Right
|
||||
</div>
|
||||
<div>Right</div>
|
||||
</Grid.CenteredRow>
|
||||
</Grid.Column>
|
||||
</>
|
||||
|
@ -16,16 +16,10 @@ export function Column({
|
||||
}: Props): ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
className,
|
||||
'mailpoet-grid-column',
|
||||
{
|
||||
className={classnames(className, 'mailpoet-grid-column', {
|
||||
[`mailpoet-grid-column-${dimension}`]: dimension,
|
||||
[`mailpoet-grid-column-${align}`]: align,
|
||||
},
|
||||
)
|
||||
}
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -14,15 +14,10 @@ export function SpaceBetween({
|
||||
}: Props): ReactElement {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
classnames(
|
||||
className,
|
||||
'mailpoet-grid-space-between',
|
||||
{
|
||||
[`mailpoet-grid-space-between-vertical-${verticalAlign}`]: verticalAlign,
|
||||
},
|
||||
)
|
||||
}
|
||||
className={classnames(className, 'mailpoet-grid-space-between', {
|
||||
[`mailpoet-grid-space-between-vertical-${verticalAlign}`]:
|
||||
verticalAlign,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -7,9 +7,16 @@ type Props = {
|
||||
automationId?: string;
|
||||
};
|
||||
|
||||
export function ThreeColumns({ children, className, automationId }: Props): ReactElement {
|
||||
export function ThreeColumns({
|
||||
children,
|
||||
className,
|
||||
automationId,
|
||||
}: Props): ReactElement {
|
||||
return (
|
||||
<div className={classnames(className, 'mailpoet-grid-three-columns')} data-automation-id={automationId}>
|
||||
<div
|
||||
className={classnames(className, 'mailpoet-grid-three-columns')}
|
||||
data-automation-id={automationId}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
@ -6,8 +6,8 @@ function KeyValueTable(props) {
|
||||
<tbody>
|
||||
{props.rows.map((row) => (
|
||||
<tr key={`row_${row.key}`}>
|
||||
<td className="row-title">{ row.key }</td>
|
||||
<td>{ row.value }</td>
|
||||
<td className="row-title">{row.key}</td>
|
||||
<td>{row.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@ -17,14 +17,16 @@ function KeyValueTable(props) {
|
||||
|
||||
KeyValueTable.propTypes = {
|
||||
max_width: PropTypes.string,
|
||||
rows: PropTypes.arrayOf(PropTypes.shape({
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.element,
|
||||
]).isRequired,
|
||||
})).isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
KeyValueTable.defaultProps = {
|
||||
|
@ -14,7 +14,10 @@ MailPoet.I18n.add('openedStatTooltipAverage', 'under 10%');
|
||||
MailPoet.I18n.add('clickedStatTooltipExcellent', 'above 3%');
|
||||
MailPoet.I18n.add('clickedStatTooltipGood', 'between 1 and 3%');
|
||||
MailPoet.I18n.add('clickedStatTooltipAverage', 'under 1%');
|
||||
MailPoet.I18n.add('revenueStatsTooltipShort', 'Revenues by customer who clicked on this email in the last 2 weeks.');
|
||||
MailPoet.I18n.add(
|
||||
'revenueStatsTooltipShort',
|
||||
'Revenues by customer who clicked on this email in the last 2 weeks.',
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'Listing',
|
||||
@ -36,11 +39,26 @@ export function NewsletterStatsComponent() {
|
||||
|
||||
<Heading level={3}>With badges and revenues</Heading>
|
||||
|
||||
<NewsletterStats opened={1} clicked={0.1} revenues="10€" newsletterId={4} />
|
||||
<NewsletterStats
|
||||
opened={1}
|
||||
clicked={0.1}
|
||||
revenues="10€"
|
||||
newsletterId={4}
|
||||
/>
|
||||
<div className="mailpoet-gap" />
|
||||
<NewsletterStats opened={11} clicked={1.1} revenues="100€" newsletterId={5} />
|
||||
<NewsletterStats
|
||||
opened={11}
|
||||
clicked={1.1}
|
||||
revenues="100€"
|
||||
newsletterId={5}
|
||||
/>
|
||||
<div className="mailpoet-gap" />
|
||||
<NewsletterStats opened={31} clicked={3.1} revenues="1000€" newsletterId={6} />
|
||||
<NewsletterStats
|
||||
opened={31}
|
||||
clicked={3.1}
|
||||
revenues="1000€"
|
||||
newsletterId={6}
|
||||
/>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
@ -56,11 +74,29 @@ export function NewsletterStatsComponent() {
|
||||
|
||||
<Heading level={3}>No badges, with revenues</Heading>
|
||||
|
||||
<NewsletterStats hideBadges opened={1} clicked={0.1} revenues="10€" newsletterId={10} />
|
||||
<NewsletterStats
|
||||
hideBadges
|
||||
opened={1}
|
||||
clicked={0.1}
|
||||
revenues="10€"
|
||||
newsletterId={10}
|
||||
/>
|
||||
<div className="mailpoet-gap" />
|
||||
<NewsletterStats hideBadges opened={11} clicked={1.1} revenues="100€" newsletterId={11} />
|
||||
<NewsletterStats
|
||||
hideBadges
|
||||
opened={11}
|
||||
clicked={1.1}
|
||||
revenues="100€"
|
||||
newsletterId={11}
|
||||
/>
|
||||
<div className="mailpoet-gap" />
|
||||
<NewsletterStats hideBadges opened={31} clicked={3.1} revenues="1000€" newsletterId={12} />
|
||||
<NewsletterStats
|
||||
hideBadges
|
||||
opened={31}
|
||||
clicked={3.1}
|
||||
revenues="1000€"
|
||||
newsletterId={12}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -40,7 +40,12 @@ export function NewsletterStatuses() {
|
||||
<NewsletterStatus total={200} processed={0} />
|
||||
<NewsletterStatus total={400} processed={150} />
|
||||
<NewsletterStatus scheduledFor={inPast} total={300} processed={270} />
|
||||
<NewsletterStatus scheduledFor={inPast} total={300} processed={270} isPaused />
|
||||
<NewsletterStatus
|
||||
scheduledFor={inPast}
|
||||
total={300}
|
||||
processed={270}
|
||||
isPaused
|
||||
/>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
|
@ -1,2 +1,5 @@
|
||||
export { default as NewsletterStats } from './newsletter_stats';
|
||||
export { default as NewsletterStatus, ScheduledIcon } from './newsletter_status';
|
||||
export {
|
||||
default as NewsletterStatus,
|
||||
ScheduledIcon,
|
||||
} from './newsletter_status';
|
||||
|
@ -32,8 +32,7 @@ function NewsletterStats({
|
||||
%
|
||||
<br />
|
||||
<span className="mailpoet-listing-stats-percentages-opens">
|
||||
{openedDisplay}
|
||||
%
|
||||
{openedDisplay}%
|
||||
</span>
|
||||
</div>
|
||||
{!hideBadges && (
|
||||
@ -53,12 +52,10 @@ function NewsletterStats({
|
||||
const revenuesTooltipId = `revenues-${newsletterId || '0'}`;
|
||||
revenueStats = (
|
||||
<div>
|
||||
<Tag data-tip data-for={revenuesTooltipId}>{revenues}</Tag>
|
||||
<Tooltip
|
||||
place="top"
|
||||
multiline
|
||||
id={revenuesTooltipId}
|
||||
>
|
||||
<Tag data-tip data-for={revenuesTooltipId}>
|
||||
{revenues}
|
||||
</Tag>
|
||||
<Tooltip place="top" multiline id={revenuesTooltipId}>
|
||||
<div className="mailpoet-listing-stats-tooltip-content">
|
||||
{MailPoet.I18n.t('revenueStatsTooltipShort')}
|
||||
</div>
|
||||
@ -68,7 +65,10 @@ function NewsletterStats({
|
||||
}
|
||||
|
||||
if (wrapContentInLink) {
|
||||
clickedAndOpenedStats = wrapContentInLink(clickedAndOpenedStats, 'opened-and-clicked');
|
||||
clickedAndOpenedStats = wrapContentInLink(
|
||||
clickedAndOpenedStats,
|
||||
'opened-and-clicked',
|
||||
);
|
||||
revenueStats = wrapContentInLink(revenueStats, 'revenue');
|
||||
}
|
||||
|
||||
|
@ -22,15 +22,10 @@ function Badge({
|
||||
}: BadgeProps) {
|
||||
return (
|
||||
<span>
|
||||
<Tag
|
||||
isInverted={isInverted}
|
||||
variant={type}
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
>
|
||||
<Tag isInverted={isInverted} variant={type} data-tip data-for={tooltipId}>
|
||||
{name}
|
||||
</Tag>
|
||||
{ tooltip && (
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
place={tooltipPlace || 'top'}
|
||||
multiline
|
||||
@ -38,7 +33,7 @@ function Badge({
|
||||
>
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
) }
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -13,11 +13,7 @@ type StatsBadgeProps = {
|
||||
const stats = {
|
||||
opened: {
|
||||
badgeRanges: [30, 10, 0],
|
||||
badgeTypes: [
|
||||
'excellent',
|
||||
'good',
|
||||
'average',
|
||||
],
|
||||
badgeTypes: ['excellent', 'good', 'average'],
|
||||
tooltipText: [
|
||||
MailPoet.I18n.t('openedStatTooltipExcellent'),
|
||||
MailPoet.I18n.t('openedStatTooltipGood'),
|
||||
@ -26,11 +22,7 @@ const stats = {
|
||||
},
|
||||
clicked: {
|
||||
badgeRanges: [3, 1, 0],
|
||||
badgeTypes: [
|
||||
'excellent',
|
||||
'good',
|
||||
'average',
|
||||
],
|
||||
badgeTypes: ['excellent', 'good', 'average'],
|
||||
tooltipText: [
|
||||
MailPoet.I18n.t('clickedStatTooltipExcellent'),
|
||||
MailPoet.I18n.t('clickedStatTooltipGood'),
|
||||
@ -92,24 +84,15 @@ export function StatsBadge(props: StatsBadgeProps) {
|
||||
{badge.tooltipTitle.toUpperCase()}
|
||||
</div>
|
||||
<div className="mailpoet-listing-stats-tooltip-content">
|
||||
<Badge
|
||||
type="excellent"
|
||||
name={badges.excellent.name}
|
||||
/>
|
||||
<Badge type="excellent" name={badges.excellent.name} />
|
||||
{' : '}
|
||||
{stat.tooltipText[0]}
|
||||
<br />
|
||||
<Badge
|
||||
type="good"
|
||||
name={badges.good.name}
|
||||
/>
|
||||
<Badge type="good" name={badges.good.name} />
|
||||
{' : '}
|
||||
{stat.tooltipText[1]}
|
||||
<br />
|
||||
<Badge
|
||||
type="average"
|
||||
name={badges.average.name}
|
||||
/>
|
||||
<Badge type="average" name={badges.average.name} />
|
||||
{' : '}
|
||||
{stat.tooltipText[2]}
|
||||
</div>
|
||||
|
@ -1,8 +1,6 @@
|
||||
import MailPoet from 'mailpoet';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
addDays, differenceInMinutes, isFuture, isPast,
|
||||
} from 'date-fns';
|
||||
import { addDays, differenceInMinutes, isFuture, isPast } from 'date-fns';
|
||||
import t from 'common/functions/t';
|
||||
import Tooltip from '../tooltip/tooltip';
|
||||
|
||||
@ -15,8 +13,18 @@ function CircularProgress({ percentage }: CircularProgressProps) {
|
||||
const filled = perimeter * (percentage / 100);
|
||||
const empty = perimeter - filled;
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="8" className="mailpoet-listing-status-percentage-background" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="8"
|
||||
className="mailpoet-listing-status-percentage-background"
|
||||
/>
|
||||
<circle
|
||||
r="8"
|
||||
cx="12"
|
||||
@ -32,8 +40,17 @@ function CircularProgress({ percentage }: CircularProgressProps) {
|
||||
|
||||
export function ScheduledIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path className="mailpoet-listing-status-scheduled-icon" strokeLinecap="round" d="M12 7L12 12 15 15" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
className="mailpoet-listing-status-scheduled-icon"
|
||||
strokeLinecap="round"
|
||||
d="M12 7L12 12 15 15"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@ -55,18 +72,20 @@ function NewsletterStatus({
|
||||
}: NewsletterStatusProps) {
|
||||
const unknown = !scheduledFor && !processed && !total;
|
||||
const scheduled = scheduledFor && isFuture(scheduledFor);
|
||||
const inProgress = (!scheduledFor || isPast(scheduledFor)) && processed < total;
|
||||
const inProgress =
|
||||
(!scheduledFor || isPast(scheduledFor)) && processed < total;
|
||||
const sent = (!scheduledFor || isPast(scheduledFor)) && processed >= total;
|
||||
const sentWithoutQueue = status === 'sent' && total === undefined;
|
||||
let percentage = 0;
|
||||
let label : string | JSX.Element = t('notSentYet');
|
||||
let label: string | JSX.Element = t('notSentYet');
|
||||
if (scheduled) {
|
||||
const scheduledDate = MailPoet.Date.short(scheduledFor);
|
||||
const scheduledTime = MailPoet.Date.time(scheduledFor);
|
||||
const now = new Date();
|
||||
const tomorrow = addDays(now, 1);
|
||||
const isScheduledForToday = MailPoet.Date.short(now) === scheduledDate;
|
||||
const isScheduledForTomorrow = MailPoet.Date.short(tomorrow) === scheduledDate;
|
||||
const isScheduledForTomorrow =
|
||||
MailPoet.Date.short(tomorrow) === scheduledDate;
|
||||
if (isScheduledForToday || isScheduledForTomorrow) {
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const dateWord = isScheduledForToday ? t('today') : t('tomorrow');
|
||||
@ -99,10 +118,14 @@ function NewsletterStatus({
|
||||
percentage = 100;
|
||||
}
|
||||
} else if (inProgress) {
|
||||
label = `${MailPoet.Num.toLocaleFixed(processed)} / ${MailPoet.Num.toLocaleFixed(total)}`;
|
||||
label = `${MailPoet.Num.toLocaleFixed(
|
||||
processed,
|
||||
)} / ${MailPoet.Num.toLocaleFixed(total)}`;
|
||||
percentage = 100 * (processed / total);
|
||||
} else if (sent) {
|
||||
label = `${MailPoet.Num.toLocaleFixed(total)} / ${MailPoet.Num.toLocaleFixed(total)}`;
|
||||
label = `${MailPoet.Num.toLocaleFixed(
|
||||
total,
|
||||
)} / ${MailPoet.Num.toLocaleFixed(total)}`;
|
||||
percentage = 100;
|
||||
} else if (sentWithoutQueue) {
|
||||
label = t('sent');
|
||||
@ -112,7 +135,8 @@ function NewsletterStatus({
|
||||
label = t('paused');
|
||||
}
|
||||
return (
|
||||
<div className={classNames({
|
||||
<div
|
||||
className={classNames({
|
||||
'mailpoet-listing-status': true,
|
||||
'mailpoet-listing-status-unknown': unknown,
|
||||
'mailpoet-listing-status-scheduled': scheduled,
|
||||
|
@ -9,24 +9,16 @@ export function Loaders() {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
Default loader:
|
||||
{' '}
|
||||
<Loader />
|
||||
Default loader: <Loader />
|
||||
</p>
|
||||
<p>
|
||||
Light loader:
|
||||
{' '}
|
||||
<Loader variant="light" />
|
||||
Light loader: <Loader variant="light" />
|
||||
</p>
|
||||
<p>
|
||||
Dark loader:
|
||||
{' '}
|
||||
<Loader variant="dark" />
|
||||
Dark loader: <Loader variant="dark" />
|
||||
</p>
|
||||
<p>
|
||||
bigger loader:
|
||||
{' '}
|
||||
<Loader size={64} />
|
||||
bigger loader: <Loader size={64} />
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
@ -6,21 +6,50 @@ import Heading from '../../typography/heading/heading';
|
||||
const shortContent = (
|
||||
<>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
||||
<p>Morbi libero sapien, tristique sollicitudin lobortis id, viverra id libero.</p>
|
||||
<p>
|
||||
Morbi libero sapien, tristique sollicitudin lobortis id, viverra id
|
||||
libero.
|
||||
</p>
|
||||
<p>Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus.</p>
|
||||
</>
|
||||
);
|
||||
|
||||
const longContent = (
|
||||
<>
|
||||
<p>{'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(20)}</p>
|
||||
<p>{'Morbi libero sapien, tristique sollicitudin lobortis id, viverra id libero. '.repeat(20)}</p>
|
||||
<p>{'Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. '.repeat(20)}</p>
|
||||
<p>
|
||||
{'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '.repeat(20)}
|
||||
</p>
|
||||
<p>
|
||||
{'Morbi libero sapien, tristique sollicitudin lobortis id, viverra id libero. '.repeat(
|
||||
20,
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{'Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. '.repeat(
|
||||
20,
|
||||
)}
|
||||
</p>
|
||||
<p>{'Vivamus ac leo pretium faucibus.'.repeat(20)}</p>
|
||||
<p>{'Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. '.repeat(20)}</p>
|
||||
<p>{'Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. '.repeat(20)}</p>
|
||||
<p>{'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. '.repeat(20)}</p>
|
||||
<p>{'Cras pede libero, dapibus nec, pretium sit amet, tempor quis. '.repeat(20)}</p>
|
||||
<p>
|
||||
{'Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. '.repeat(
|
||||
20,
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{'Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. '.repeat(
|
||||
20,
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. '.repeat(
|
||||
20,
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{'Cras pede libero, dapibus nec, pretium sit amet, tempor quis. '.repeat(
|
||||
20,
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -55,7 +84,7 @@ function ModalWrapper({
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
) }
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@ -65,25 +94,71 @@ export function Modals() {
|
||||
<>
|
||||
<Heading level={3}>Modal with short text</Heading>
|
||||
<ModalWrapper buttonCaption="Show modal with title, with close button" />
|
||||
<ModalWrapper buttonCaption="Show modal with title, without close button" isDismissible={false} />
|
||||
<ModalWrapper buttonCaption="Show modal without title, with close button" title={null} />
|
||||
<ModalWrapper buttonCaption="Show modal without title, without close button" title={null} isDismissible={false} />
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal with title, without close button"
|
||||
isDismissible={false}
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal without title, with close button"
|
||||
title={null}
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal without title, without close button"
|
||||
title={null}
|
||||
isDismissible={false}
|
||||
/>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
<Heading level={3}>Modal with long text</Heading>
|
||||
<ModalWrapper buttonCaption="Show modal with title, with close button" content={longContent} />
|
||||
<ModalWrapper buttonCaption="Show modal with title, without close button" isDismissible={false} content={longContent} />
|
||||
<ModalWrapper buttonCaption="Show modal without title, with close button" title={null} content={longContent} />
|
||||
<ModalWrapper buttonCaption="Show modal without title, without close button" title={null} isDismissible={false} content={longContent} />
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal with title, with close button"
|
||||
content={longContent}
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal with title, without close button"
|
||||
isDismissible={false}
|
||||
content={longContent}
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal without title, with close button"
|
||||
title={null}
|
||||
content={longContent}
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal without title, without close button"
|
||||
title={null}
|
||||
isDismissible={false}
|
||||
content={longContent}
|
||||
/>
|
||||
|
||||
<div className="mailpoet-gap" />
|
||||
|
||||
<Heading level={3}>Full-screen modal</Heading>
|
||||
<ModalWrapper buttonCaption="Show modal with title, with close button" content={longContent} fullScreen />
|
||||
<ModalWrapper buttonCaption="Show modal with title, without close button" isDismissible={false} content={longContent} fullScreen />
|
||||
<ModalWrapper buttonCaption="Show modal without title, with close button" title={null} content={longContent} fullScreen />
|
||||
<ModalWrapper buttonCaption="Show modal without title, without close button" title={null} isDismissible={false} content={longContent} fullScreen />
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal with title, with close button"
|
||||
content={longContent}
|
||||
fullScreen
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal with title, without close button"
|
||||
isDismissible={false}
|
||||
content={longContent}
|
||||
fullScreen
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal without title, with close button"
|
||||
title={null}
|
||||
content={longContent}
|
||||
fullScreen
|
||||
/>
|
||||
<ModalWrapper
|
||||
buttonCaption="Show modal without title, without close button"
|
||||
title={null}
|
||||
isDismissible={false}
|
||||
content={longContent}
|
||||
fullScreen
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -7,11 +7,7 @@ type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
function ModalFrame({
|
||||
fullScreen = false,
|
||||
className = '',
|
||||
children,
|
||||
}: Props) {
|
||||
function ModalFrame({ fullScreen = false, className = '', children }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
|
@ -7,9 +7,7 @@ type Props = {
|
||||
function ModalHeader({ title }: Props) {
|
||||
return (
|
||||
<div className="mailpoet-modal-header">
|
||||
<Heading level={3}>
|
||||
{ title }
|
||||
</Heading>
|
||||
<Heading level={3}>{title}</Heading>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -38,14 +38,9 @@ function Modal({
|
||||
shouldCloseOnClickOutside={shouldCloseOnClickOutside}
|
||||
className={overlayClassName}
|
||||
>
|
||||
<ModalFrame
|
||||
className={contentClassName}
|
||||
fullScreen={fullScreen}
|
||||
>
|
||||
{ title && (
|
||||
<ModalHeader title={title} />
|
||||
) }
|
||||
{ isDismissible && (
|
||||
<ModalFrame className={contentClassName} fullScreen={fullScreen}>
|
||||
{title && <ModalHeader title={title} />}
|
||||
{isDismissible && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRequestClose}
|
||||
@ -54,12 +49,9 @@ function Modal({
|
||||
>
|
||||
{ModalCloseIcon}
|
||||
</button>
|
||||
) }
|
||||
<div
|
||||
className="mailpoet-modal-content"
|
||||
role="document"
|
||||
>
|
||||
{ children }
|
||||
)}
|
||||
<div className="mailpoet-modal-content" role="document">
|
||||
{children}
|
||||
</div>
|
||||
</ModalFrame>
|
||||
</ModalOverlay>,
|
||||
|
@ -31,25 +31,27 @@ const getBannerMessage = (translationKey: string) => {
|
||||
{ReactStringReplace(
|
||||
message,
|
||||
/(\[subscribersCount]|\[subscribersLimit])/g,
|
||||
(match) => ((match === '[subscribersCount]') ? subscribersCount : subscribersLimit),
|
||||
(match) =>
|
||||
match === '[subscribersCount]' ? subscribersCount : subscribersLimit,
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const getCtaButton = (translationKey: string, link: string, target = '_blank') => (
|
||||
<Button
|
||||
href={link}
|
||||
target={target}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
const getCtaButton = (
|
||||
translationKey: string,
|
||||
link: string,
|
||||
target = '_blank',
|
||||
) => (
|
||||
<Button href={link} target={target} rel="noopener noreferrer">
|
||||
{MailPoet.I18n.t(translationKey)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
function PremiumBannerWithUpgrade(
|
||||
{ message, actionButton }: Props,
|
||||
) : JSX.Element {
|
||||
function PremiumBannerWithUpgrade({
|
||||
message,
|
||||
actionButton,
|
||||
}: Props): JSX.Element {
|
||||
let bannerMessage: ReactNode;
|
||||
let ctaButton: ReactNode;
|
||||
|
||||
@ -57,14 +59,25 @@ function PremiumBannerWithUpgrade(
|
||||
bannerMessage = getBannerMessage('premiumFeatureDescription');
|
||||
|
||||
ctaButton = isPremiumPluginInstalled
|
||||
? getCtaButton('premiumFeatureButtonActivatePremium', premiumPluginActivationUrl, '_self')
|
||||
: getCtaButton('premiumFeatureButtonDownloadPremium', premiumPluginDownloadUrl);
|
||||
? getCtaButton(
|
||||
'premiumFeatureButtonActivatePremium',
|
||||
premiumPluginActivationUrl,
|
||||
'_self',
|
||||
)
|
||||
: getCtaButton(
|
||||
'premiumFeatureButtonDownloadPremium',
|
||||
premiumPluginDownloadUrl,
|
||||
);
|
||||
} else if (subscribersLimitReached) {
|
||||
bannerMessage = getBannerMessage('premiumFeatureDescriptionSubscribersLimitReached');
|
||||
bannerMessage = getBannerMessage(
|
||||
'premiumFeatureDescriptionSubscribersLimitReached',
|
||||
);
|
||||
|
||||
const link = anyValidKey
|
||||
? MailPoet.MailPoetComUrlFactory.getUpgradeUrl(pluginPartialKey)
|
||||
: MailPoet.MailPoetComUrlFactory.getPurchasePlanUrl(+subscribersCount + 1);
|
||||
: MailPoet.MailPoetComUrlFactory.getPurchasePlanUrl(
|
||||
+subscribersCount + 1,
|
||||
);
|
||||
|
||||
ctaButton = getCtaButton('premiumFeatureButtonUpgradePlan', link);
|
||||
} else {
|
||||
|
@ -11,18 +11,16 @@ export function PremiumsRequired() {
|
||||
<div>
|
||||
<PremiumRequired
|
||||
title="This is a Premium Feature"
|
||||
message={(
|
||||
message={
|
||||
<p>
|
||||
Learn more about your subscribers and optimize your campaigns. See who
|
||||
opened your emails, which links they clicked, and then use the data to make y
|
||||
our emails even better. And if you run a WooCommerce store, you will also
|
||||
see the revenue earned per email.
|
||||
<a href="#">
|
||||
Learn more.
|
||||
</a>
|
||||
Learn more about your subscribers and optimize your campaigns. See
|
||||
who opened your emails, which links they clicked, and then use the
|
||||
data to make y our emails even better. And if you run a WooCommerce
|
||||
store, you will also see the revenue earned per email.
|
||||
<a href="#">Learn more.</a>
|
||||
</p>
|
||||
)}
|
||||
actionButton={(<Button href="#">Sign Up</Button>)}
|
||||
}
|
||||
actionButton={<Button href="#">Sign Up</Button>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -13,15 +13,11 @@ function PremiumRequired({ title, message, actionButton }: Props) {
|
||||
<div className="mailpoet-premium-required">
|
||||
<div className="mailpoet-premium-required-message">
|
||||
<Heading level={5}>
|
||||
<Badge title="Premium" />
|
||||
{' '}
|
||||
{title}
|
||||
<Badge title="Premium" /> {title}
|
||||
</Heading>
|
||||
{message}
|
||||
</div>
|
||||
<div className="mailpoet-premium-required-button">
|
||||
{actionButton}
|
||||
</div>
|
||||
<div className="mailpoet-premium-required-button">{actionButton}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,19 @@
|
||||
export default function DesktopIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
>
|
||||
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g className="mailpoet_preview_icon_fill" transform="translate(-712.000000, -64.000000)" fillRule="nonzero">
|
||||
<g
|
||||
className="mailpoet_preview_icon_fill"
|
||||
transform="translate(-712.000000, -64.000000)"
|
||||
fillRule="nonzero"
|
||||
>
|
||||
<g transform="translate(712.000000, 64.000000)">
|
||||
<g>
|
||||
<path d="M13.965,20.5 C14.1138804,20.5 14.2550143,20.5663566 14.35,20.681 L14.35,20.681 L16.449,23.213 C16.5561084,23.3657981 16.5692196,23.5655248 16.4830056,23.7310137 C16.3967915,23.8965027 16.2255996,24.0002137 16.039,24 L16.039,24 L7.96,24 C7.7734004,24.0002137 7.60220849,23.8965027 7.51599443,23.7310137 C7.42978036,23.5655248 7.44289164,23.3657981 7.55,23.213 L7.55,23.213 L9.65,20.681 C9.74498572,20.5663566 9.88611956,20.5 10.035,20.5 L10.035,20.5 Z M22.5,-4.08562073e-14 C23.3284271,-4.08562073e-14 24,0.671572875 24,1.5 L24,1.5 L24,17.5 C24,18.3284271 23.3284271,19 22.5,19 L22.5,19 L1.5,19 C0.671572875,19 0,18.3284271 0,17.5 L0,17.5 L0,1.5 C0,0.671572875 0.671572875,-4.08562073e-14 1.5,-4.08562073e-14 L1.5,-4.08562073e-14 Z M21.5,2 L2.5,2 C2.22385763,2 2,2.22385763 2,2.5 L2,2.5 L2,14.5 C2,14.7761424 2.22385763,15 2.5,15 L2.5,15 L21.5,15 C21.7761424,15 22,14.7761424 22,14.5 L22,14.5 L22,2.5 C22,2.22385763 21.7761424,2 21.5,2 L21.5,2 Z" />
|
||||
|
@ -1,11 +1,25 @@
|
||||
export default function MobileIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="16px" height="24px" viewBox="0 0 16 24" version="1.1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="16px"
|
||||
height="24px"
|
||||
viewBox="0 0 16 24"
|
||||
version="1.1"
|
||||
>
|
||||
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g className="mailpoet_preview_icon_fill" transform="translate(-760.000000, -64.000000)" fillRule="nonzero">
|
||||
<g
|
||||
className="mailpoet_preview_icon_fill"
|
||||
transform="translate(-760.000000, -64.000000)"
|
||||
fillRule="nonzero"
|
||||
>
|
||||
<g transform="translate(712.000000, 64.000000)">
|
||||
<g transform="translate(48.000000, 0.000000)">
|
||||
<path d="M16,3 C16,1.34314575 14.6568542,0 13,0 L3,0 C1.34314575,0 0,1.34314575 0,3 L0,21 C0,22.6568542 1.34314575,24 3,24 L13,24 C14.6568542,24 16,22.6568542 16,21 L16,3 Z M14,5.5 L14,17.5 C14,18.0522847 13.5522847,18.5 13,18.5 L3,18.5 C2.44771525,18.5 2,18.0522847 2,17.5 L2,5.5 C2,4.94771525 2.44771525,4.5 3,4.5 L13,4.5 C13.5522847,4.5 14,4.94771525 14,5.5 Z M7,21 C7,20.4477153 7.44771525,20 8,20 C8.55228475,20 9,20.4477153 9,21 C9,21.5522847 8.55228475,22 8,22 C7.44771525,22 7,21.5522847 7,21 Z" id="Shape" />
|
||||
<path
|
||||
d="M16,3 C16,1.34314575 14.6568542,0 13,0 L3,0 C1.34314575,0 0,1.34314575 0,3 L0,21 C0,22.6568542 1.34314575,24 3,24 L13,24 C14.6568542,24 16,22.6568542 16,21 L16,3 Z M14,5.5 L14,17.5 C14,18.0522847 13.5522847,18.5 13,18.5 L3,18.5 C2.44771525,18.5 2,18.0522847 2,17.5 L2,5.5 C2,4.94771525 2.44771525,4.5 3,4.5 L13,4.5 C13.5522847,4.5 14,4.94771525 14,5.5 Z M7,21 C7,20.4477153 7.44771525,20 8,20 C8.55228475,20 9,20.4477153 9,21 C9,21.5522847 8.55228475,22 8,22 C7.44771525,22 7,21.5522847 7,21 Z"
|
||||
id="Shape"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
@ -5,11 +5,7 @@ import classnames from 'classnames';
|
||||
import MobileIcon from './mobile_icon';
|
||||
import DesktopIcon from './desktop_icon';
|
||||
|
||||
function Preview({
|
||||
children,
|
||||
onDisplayTypeChange,
|
||||
selectedDisplayType,
|
||||
}) {
|
||||
function Preview({ children, onDisplayTypeChange, selectedDisplayType }) {
|
||||
const [displayType, setDisplayType] = useState(selectedDisplayType);
|
||||
const changeType = (type) => {
|
||||
setDisplayType(type);
|
||||
@ -19,7 +15,9 @@ function Preview({
|
||||
<div className="mailpoet_browser_preview">
|
||||
<div className="mailpoet_browser_preview_toggle">
|
||||
<a
|
||||
className={classnames('mailpoet_browser_preview_icon', { mailpoet_active: displayType === 'desktop' })}
|
||||
className={classnames('mailpoet_browser_preview_icon', {
|
||||
mailpoet_active: displayType === 'desktop',
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
changeType('desktop');
|
||||
@ -31,7 +29,9 @@ function Preview({
|
||||
<DesktopIcon />
|
||||
</a>
|
||||
<a
|
||||
className={classnames('mailpoet_browser_preview_icon', { mailpoet_active: displayType === 'mobile' })}
|
||||
className={classnames('mailpoet_browser_preview_icon', {
|
||||
mailpoet_active: displayType === 'mobile',
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
changeType('mobile');
|
||||
@ -46,16 +46,22 @@ function Preview({
|
||||
<div
|
||||
className={classnames(
|
||||
'mailpoet_browser_preview_container',
|
||||
{ mailpoet_browser_preview_container_mobile: displayType !== 'desktop' },
|
||||
{ mailpoet_browser_preview_container_desktop: displayType === 'desktop' },
|
||||
{
|
||||
mailpoet_browser_preview_container_mobile:
|
||||
displayType !== 'desktop',
|
||||
},
|
||||
{
|
||||
mailpoet_browser_preview_container_desktop:
|
||||
displayType === 'desktop',
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="mailpoet_browser_preview_border">
|
||||
{children}
|
||||
<div className="mailpoet_browser_preview_border">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
{(displayType !== 'desktop') && (
|
||||
<p className="mailpoet_form_preview_disclaimer">{MailPoet.I18n.t('formPreviewMobileDisclaimer')}</p>
|
||||
{displayType !== 'desktop' && (
|
||||
<p className="mailpoet_form_preview_disclaimer">
|
||||
{MailPoet.I18n.t('formPreviewMobileDisclaimer')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -4,9 +4,9 @@ import MailPoet from 'mailpoet';
|
||||
function PrintBoolean(props) {
|
||||
return (
|
||||
<span>
|
||||
{(props.children === true && props.truthy)
|
||||
|| (props.children === false && props.falsy)
|
||||
|| (props.unknown)}
|
||||
{(props.children === true && props.truthy) ||
|
||||
(props.children === false && props.falsy) ||
|
||||
props.unknown}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -23,9 +23,10 @@ function SenderEmailAddressWarning({ emailAddress, mssActive }) {
|
||||
/(%1\$s|%2\$s|<em>.*<\/em>)/,
|
||||
(match) => {
|
||||
if (match === '%1$s') return suggestedEmailAddress;
|
||||
if (match === '%2$s') return <em key="sender-email">{ emailAddress }</em>;
|
||||
if (match === '%2$s')
|
||||
return <em key="sender-email">{emailAddress}</em>;
|
||||
return <em key="reply-to">{match.replace(/<\/?em>/g, '')}</em>;
|
||||
}
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p className="sender_email_address_warning">
|
||||
|
@ -11,16 +11,20 @@ import { ErrorResponse, isErrorResponse } from '../ajax';
|
||||
* @param {string|null} address
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const handleSave = (address: string | null) => MailPoet.Ajax.post({
|
||||
const handleSave = (address: string | null) =>
|
||||
MailPoet.Ajax.post({
|
||||
api_version: MailPoet.apiVersion,
|
||||
endpoint: 'settings',
|
||||
action: 'setAuthorizedFromAddress',
|
||||
data: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const getErrorMessage = (error: ErrorResponse['errors'][number] | null, address: string | null): string => {
|
||||
const getErrorMessage = (
|
||||
error: ErrorResponse['errors'][number] | null,
|
||||
address: string | null,
|
||||
): string => {
|
||||
if (!error) {
|
||||
return MailPoet.I18n.t('setFromAddressEmailUnknownError');
|
||||
}
|
||||
@ -55,15 +59,21 @@ const getSuccessMessage = (): JSX.Element => (
|
||||
);
|
||||
|
||||
const removeUnauthorizedEmailNotices = () => {
|
||||
const unauthorizedEmailNotice = document.querySelector('[data-notice="unauthorized-email-addresses-notice"]');
|
||||
const unauthorizedEmailNotice = document.querySelector(
|
||||
'[data-notice="unauthorized-email-addresses-notice"]',
|
||||
);
|
||||
if (unauthorizedEmailNotice) {
|
||||
unauthorizedEmailNotice.remove();
|
||||
}
|
||||
const unauthorizedEmailInNewsletterNotice = document.querySelector('[data-notice="unauthorized-email-in-newsletters-addresses-notice"]');
|
||||
const unauthorizedEmailInNewsletterNotice = document.querySelector(
|
||||
'[data-notice="unauthorized-email-in-newsletters-addresses-notice"]',
|
||||
);
|
||||
if (unauthorizedEmailInNewsletterNotice) {
|
||||
unauthorizedEmailInNewsletterNotice.remove();
|
||||
}
|
||||
const unauthorizedEmailInNewsletterDynamicNotice = document.querySelector('[data-id="mailpoet_authorization_error"]');
|
||||
const unauthorizedEmailInNewsletterDynamicNotice = document.querySelector(
|
||||
'[data-id="mailpoet_authorization_error"]',
|
||||
);
|
||||
if (unauthorizedEmailInNewsletterDynamicNotice) {
|
||||
unauthorizedEmailInNewsletterDynamicNotice.remove();
|
||||
}
|
||||
@ -86,8 +96,7 @@ function SetFromAddressModal({ onRequestClose, setAuthorizedAddress }: Props) {
|
||||
contentClassName="set-from-address-modal"
|
||||
>
|
||||
<p>
|
||||
{
|
||||
ReactStringReplace(
|
||||
{ReactStringReplace(
|
||||
MailPoet.I18n.t('setFromAddressModalDescription'),
|
||||
/\[link\](.*?)\[\/link\]/g,
|
||||
(match) => (
|
||||
@ -100,8 +109,7 @@ function SetFromAddressModal({ onRequestClose, setAuthorizedAddress }: Props) {
|
||||
{match}
|
||||
</a>
|
||||
),
|
||||
)
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
|
||||
<input
|
||||
@ -112,7 +120,9 @@ function SetFromAddressModal({ onRequestClose, setAuthorizedAddress }: Props) {
|
||||
data-parsley-type="email"
|
||||
onChange={(event) => {
|
||||
setAddress(event.target.value.trim() || null);
|
||||
const addressValidator = jQuery('#mailpoet-set-from-address-modal-input').parsley();
|
||||
const addressValidator = jQuery(
|
||||
'#mailpoet-set-from-address-modal-input',
|
||||
).parsley();
|
||||
addressValidator.removeError('saveError');
|
||||
}}
|
||||
/>
|
||||
@ -122,7 +132,9 @@ function SetFromAddressModal({ onRequestClose, setAuthorizedAddress }: Props) {
|
||||
type="submit"
|
||||
value={MailPoet.I18n.t('setFromAddressModalSave')}
|
||||
onClick={async () => {
|
||||
const addressValidator = jQuery('#mailpoet-set-from-address-modal-input').parsley();
|
||||
const addressValidator = jQuery(
|
||||
'#mailpoet-set-from-address-modal-input',
|
||||
).parsley();
|
||||
addressValidator.validate();
|
||||
if (!addressValidator.isValid()) {
|
||||
return;
|
||||
@ -137,9 +149,12 @@ function SetFromAddressModal({ onRequestClose, setAuthorizedAddress }: Props) {
|
||||
removeUnauthorizedEmailNotices();
|
||||
notices.success(getSuccessMessage(), { timeout: false });
|
||||
} catch (e) {
|
||||
const error = isErrorResponse(e) && e.errors[0] ? e.errors[0] : null;
|
||||
const error =
|
||||
isErrorResponse(e) && e.errors[0] ? e.errors[0] : null;
|
||||
if (error.error === 'unauthorized') {
|
||||
MailPoet.trackEvent('Unauthorized email used', { 'Unauthorized email source': 'modal' });
|
||||
MailPoet.trackEvent('Unauthorized email used', {
|
||||
'Unauthorized email source': 'modal',
|
||||
});
|
||||
}
|
||||
const message = getErrorMessage(error, address);
|
||||
addressValidator.addError('saveError', { message });
|
||||
|
@ -22,8 +22,9 @@ export function StepsWithoutTitles() {
|
||||
<Heading level={3}>{`Step ${step}`}</Heading>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta natus
|
||||
consequuntur saepe harum nesciunt eum, a nulla facilis architecto incidunt
|
||||
odio voluptas praesentium, ipsa laboriosam animi! Officiis atque odio nulla.
|
||||
consequuntur saepe harum nesciunt eum, a nulla facilis architecto
|
||||
incidunt odio voluptas praesentium, ipsa laboriosam animi! Officiis
|
||||
atque odio nulla.
|
||||
</p>
|
||||
<div>
|
||||
<Button
|
||||
@ -34,11 +35,7 @@ export function StepsWithoutTitles() {
|
||||
>
|
||||
Previous step
|
||||
</Button>
|
||||
<Button
|
||||
onClick={nextStep}
|
||||
dimension="small"
|
||||
isDisabled={step === 5}
|
||||
>
|
||||
<Button onClick={nextStep} dimension="small" isDisabled={step === 5}>
|
||||
Next step
|
||||
</Button>
|
||||
</div>
|
||||
@ -55,13 +52,18 @@ export function StepsWithTitles() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Steps count={5} current={step} titles={['First', 'Second', 'Third', 'Fourth', 'Fifth']} />
|
||||
<Steps
|
||||
count={5}
|
||||
current={step}
|
||||
titles={['First', 'Second', 'Third', 'Fourth', 'Fifth']}
|
||||
/>
|
||||
<StepsContent>
|
||||
<Heading level={3}>{`Step ${step}`}</Heading>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta natus
|
||||
consequuntur saepe harum nesciunt eum, a nulla facilis architecto incidunt
|
||||
odio voluptas praesentium, ipsa laboriosam animi! Officiis atque odio nulla.
|
||||
consequuntur saepe harum nesciunt eum, a nulla facilis architecto
|
||||
incidunt odio voluptas praesentium, ipsa laboriosam animi! Officiis
|
||||
atque odio nulla.
|
||||
</p>
|
||||
<div>
|
||||
<Button
|
||||
@ -72,11 +74,7 @@ export function StepsWithTitles() {
|
||||
>
|
||||
Previous step
|
||||
</Button>
|
||||
<Button
|
||||
onClick={nextStep}
|
||||
dimension="small"
|
||||
isDisabled={step === 5}
|
||||
>
|
||||
<Button onClick={nextStep} dimension="small" isDisabled={step === 5}>
|
||||
Next step
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -3,7 +3,9 @@ export function ContentWrapperFix() {
|
||||
<>
|
||||
<style
|
||||
/* eslint-disable-next-line react/no-danger */
|
||||
dangerouslySetInnerHTML={{ __html: '#wpbody-content { padding-top: 73px; }' }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: '#wpbody-content { padding-top: 73px; }',
|
||||
}}
|
||||
/>
|
||||
|
||||
<style
|
||||
|
@ -20,7 +20,14 @@ function Steps({ count, current, titles }: Props) {
|
||||
})}
|
||||
>
|
||||
<div className="mailpoet-step-badge">{i >= current ? i : ''}</div>
|
||||
{titles[i - 1] && <div className="mailpoet-step-title" data-title={titles[i - 1] || ''}>{titles[i - 1] || ''}</div>}
|
||||
{titles[i - 1] && (
|
||||
<div
|
||||
className="mailpoet-step-title"
|
||||
data-title={titles[i - 1] || ''}
|
||||
>
|
||||
{titles[i - 1] || ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<ContentWrapperFix />
|
||||
|
@ -8,11 +8,14 @@ type Props = {
|
||||
cacheCalculation: string;
|
||||
};
|
||||
|
||||
export function SubscribersCacheMessage({ cacheCalculation }: Props): JSX.Element {
|
||||
export function SubscribersCacheMessage({
|
||||
cacheCalculation,
|
||||
}: Props): JSX.Element {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
const datetimeDiff = new Date().getTime() - new Date(cacheCalculation).getTime();
|
||||
const minutes = Math.floor((datetimeDiff / 1000) / 60);
|
||||
const datetimeDiff =
|
||||
new Date().getTime() - new Date(cacheCalculation).getTime();
|
||||
const minutes = Math.floor(datetimeDiff / 1000 / 60);
|
||||
|
||||
const handleRecalculate = () => {
|
||||
setLoading(true);
|
||||
@ -20,9 +23,11 @@ export function SubscribersCacheMessage({ cacheCalculation }: Props): JSX.Elemen
|
||||
api_version: MailPoet.apiVersion,
|
||||
endpoint: 'settings',
|
||||
action: 'recalculateSubscribersCountsCache',
|
||||
}).done(() => {
|
||||
})
|
||||
.done(() => {
|
||||
window.location.reload();
|
||||
}).fail((response:ErrorResponse) => {
|
||||
})
|
||||
.fail((response: ErrorResponse) => {
|
||||
setErrors(response.errors.map((error) => error.message));
|
||||
setLoading(false);
|
||||
});
|
||||
@ -30,7 +35,6 @@ export function SubscribersCacheMessage({ cacheCalculation }: Props): JSX.Elemen
|
||||
|
||||
return (
|
||||
<div className="mailpoet-subscribers-cache-notice">
|
||||
|
||||
{ReactStringReplace(
|
||||
MailPoet.I18n.t('subscribersCountWereCalculatedWithMinutesAgo'),
|
||||
/<abbr>(.*?)<\/abbr>/,
|
||||
@ -52,7 +56,13 @@ export function SubscribersCacheMessage({ cacheCalculation }: Props): JSX.Elemen
|
||||
{MailPoet.I18n.t('recalculateNow')}
|
||||
</Button>
|
||||
<div className="mailpoet-gap" />
|
||||
{errors.length > 0 && <Notice type="error">{errors.map((error) => <p key={error}>{error}</p>)}</Notice>}
|
||||
{errors.length > 0 && (
|
||||
<Notice type="error">
|
||||
{errors.map((error) => (
|
||||
<p key={error}>{error}</p>
|
||||
))}
|
||||
</Notice>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -27,13 +27,16 @@ function SubscribersInPlan({
|
||||
|
||||
return (
|
||||
<div className="mailpoet-subscribers-in-plan">
|
||||
{ReactStringReplace(MailPoet.I18n.t('subscribersInPlan'), '%s', () => subscribersInPlanCount)}
|
||||
{' '}
|
||||
{ReactStringReplace(
|
||||
MailPoet.I18n.t('subscribersInPlan'),
|
||||
'%s',
|
||||
() => subscribersInPlanCount,
|
||||
)}{' '}
|
||||
<HelpTooltip
|
||||
tooltip={MailPoet.I18n.t('subscribersInPlanTooltip')}
|
||||
place="right"
|
||||
/>
|
||||
<span className="mailpoet-subscribers-in-plan-spacer">{' '}</span>
|
||||
<span className="mailpoet-subscribers-in-plan-spacer"> </span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user