diff --git a/assets/css/src/components/_welcomeWizard.scss b/assets/css/src/components/_welcomeWizard.scss index 9b6b3bbdeb..eddff3959a 100644 --- a/assets/css/src/components/_welcomeWizard.scss +++ b/assets/css/src/components/_welcomeWizard.scss @@ -121,3 +121,20 @@ position: absolute; top: -1000px; } + +.mailpoet_welcome_wizard_centered_column { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; +} + +.mailpoet_wizard_woocommerce_list { + text-align: center; + + label { + color: #595c65; + display: block; + margin-top: 10px; + } +} diff --git a/assets/js/src/wizard/steps/woo_commerce_import_list_step.jsx b/assets/js/src/wizard/steps/woo_commerce_import_list_step.jsx index 4d429da4ae..182187fbed 100644 --- a/assets/js/src/wizard/steps/woo_commerce_import_list_step.jsx +++ b/assets/js/src/wizard/steps/woo_commerce_import_list_step.jsx @@ -1,13 +1,80 @@ import PropTypes from 'prop-types'; import React from 'react'; import MailPoet from 'mailpoet'; +import ReactHtmlParser from 'react-html-parser'; -const WizardWooCommerceImportListStep = props => ( -
-

{MailPoet.I18n.t('wooCommerceListImportTitle')}

-
-); -WizardWooCommerceImportListStep.propTypes = {}; +class WizardWooCommerceImportListStep extends React.Component { + constructor(props) { + super(props); + + this.state = { + importType: null, + }; + + this.handleOptionChange = this.handleOptionChange.bind(this); + this.submit = this.submit.bind(this); + } + + handleOptionChange(event) { + this.setState({ + importType: event.target.value, + }); + } + + submit(event) { + event.preventDefault(); + if (!this.state.importType) return false; + this.props.submitForm(this.state.importType); + return false; + } + + render() { + return ( +
+

{MailPoet.I18n.t('wooCommerceListImportTitle')}

+

{MailPoet.I18n.t('wooCommerceListImportInfo1')}

+

{MailPoet.I18n.t('wooCommerceListImportInfo2')}

+

{MailPoet.I18n.t('wooCommerceListImportInfo3')}

+
+ + +

{MailPoet.I18n.t('wooCommerceListImportInfo4')}

+ +
+
+ ); + } +} + +WizardWooCommerceImportListStep.propTypes = { + submitForm: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, +}; export default WizardWooCommerceImportListStep; diff --git a/assets/js/src/wizard/woocommerce_import_controller.jsx b/assets/js/src/wizard/woocommerce_import_controller.jsx index cadbffdefc..7f74384dd8 100644 --- a/assets/js/src/wizard/woocommerce_import_controller.jsx +++ b/assets/js/src/wizard/woocommerce_import_controller.jsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import MailPoet from 'mailpoet'; import WooCommerceImportListStep from './steps/woo_commerce_import_list_step.jsx'; @@ -6,26 +5,42 @@ import WooCommerceImportListStep from './steps/woo_commerce_import_list_step.jsx class WooCommerceImportController extends React.Component { constructor(props) { super(props); - this.state = { loading: false, }; this.updateSettings = this.updateSettings.bind(this); + this.scheduleImport = this.scheduleImport.bind(this); + this.finishWizard = this.finishWizard.bind(this); + this.submit = this.submit.bind(this); } - finishWizard() { this.setState({ loading: true }); window.location = window.finish_wizard_url; } updateSettings(data) { - this.setState({ loading: true }); return MailPoet.Ajax.post({ api_version: window.mailpoet_api_version, endpoint: 'settings', action: 'set', data, + }).fail((response) => { + this.setState({ loading: false }); + if (response.errors.length > 0) { + MailPoet.Notice.error( + response.errors.map(error => error.message), + { scroll: true } + ); + } + }); + } + + scheduleImport() { + return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, + endpoint: 'importExport', + action: 'setupWooCommerceInitialImport', }).then(() => this.setState({ loading: false })).fail((response) => { this.setState({ loading: false }); if (response.errors.length > 0) { @@ -37,13 +52,22 @@ class WooCommerceImportController extends React.Component { }); } + submit(importType) { + this.setState({ loading: true }); + const settings = { + 'woocommerce.import_screen_displayed': 1, + 'mailpoet_subscribe_old_woocommerce_customers.enabled': importType === 'subscribed' ? 1 : 0, + }; + this.updateSettings(settings).then(this.scheduleImport).then(this.finishWizard); + } + render() { return (
MailPoet logo
- +
); } diff --git a/lib/API/JSON/v1/ImportExport.php b/lib/API/JSON/v1/ImportExport.php index 76a5ef16ab..44f82de3b0 100644 --- a/lib/API/JSON/v1/ImportExport.php +++ b/lib/API/JSON/v1/ImportExport.php @@ -2,8 +2,11 @@ namespace MailPoet\API\JSON\v1; +use Carbon\Carbon; use MailPoet\API\JSON\Endpoint as APIEndpoint; use MailPoet\Config\AccessControl; +use MailPoet\Cron\Workers\WooCommerceSync; +use MailPoet\Models\ScheduledTask; use MailPoet\Models\Segment; use MailPoet\Subscribers\ImportExport\Import\MailChimp; @@ -78,4 +81,27 @@ class ImportExport extends APIEndpoint { )); } } + + function setupWooCommerceInitialImport() { + try { + $task = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE) + ->whereRaw('status = ? OR status IS NULL', [ScheduledTask::STATUS_SCHEDULED]) + ->findOne(); + if ($task && $task->status === null) { + return $this->successResponse(); + } + if (!$task) { + $task = ScheduledTask::create(); + $task->type = WooCommerceSync::TASK_TYPE; + $task->status = ScheduledTask::STATUS_SCHEDULED; + } + $task->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp')); + $task->save(); + return $this->successResponse(); + } catch (\Exception $e) { + return $this->errorResponse([ + $e->getCode() => $e->getMessage() + ]); + } + } } diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php index bdecacbcd0..cf5fce1aab 100644 --- a/lib/Config/Menu.php +++ b/lib/Config/Menu.php @@ -410,7 +410,7 @@ class Menu { function wooCommerceListImport() { if ((bool)(defined('DOING_AJAX') && DOING_AJAX)) return; $data = [ - + 'finish_wizard_url' => $this->wp->adminUrl('admin.php?page=' . self::MAIN_PAGE_SLUG), ]; $this->displayPage('woocommerce_list_import.html', $data); } diff --git a/tests/integration/API/JSON/v1/ImportExportTest.php b/tests/integration/API/JSON/v1/ImportExportTest.php new file mode 100644 index 0000000000..595cf69190 --- /dev/null +++ b/tests/integration/API/JSON/v1/ImportExportTest.php @@ -0,0 +1,74 @@ +endpoint = ContainerWrapper::getInstance()->get(ImportExport::class); + ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->deleteMany(); + } + + function testItSchedulesTaskWhenNoneExistss() { + $response = $this->endpoint->setupWooCommerceInitialImport(); + expect($response->status)->equals(200); + $task = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->findOne(); + expect($task->status)->equals(ScheduledTask::STATUS_SCHEDULED); + $now = time(); + $scheduled_at = new Carbon($task->scheduled_at); + expect($scheduled_at->timestamp)->greaterOrEquals($now - 1); + expect($scheduled_at->timestamp)->lessOrEquals($now + 1); + } + + function testItReschedulesScheduledTaskToNow() { + $original_schedule = Carbon::createFromTimestamp(time() + 3000); + $this->createTask(WooCommerceSync::TASK_TYPE, ScheduledTask::STATUS_SCHEDULED, $original_schedule); + $this->endpoint->setupWooCommerceInitialImport(); + $task = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->findOne(); + expect($task->status)->equals(ScheduledTask::STATUS_SCHEDULED); + $now = time(); + $scheduled_at = new Carbon($task->scheduled_at); + expect($scheduled_at->timestamp)->greaterOrEquals($now - 1); + expect($scheduled_at->timestamp)->lessOrEquals($now + 1); + $task_count = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->count(); + expect($task_count)->equals(1); + } + + function testItDoesNothingForRunningTask() { + $this->createTask(WooCommerceSync::TASK_TYPE, null); + $this->endpoint->setupWooCommerceInitialImport(); + $task = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->findOne(); + expect($task->status)->equals(null); + $task_count = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->count(); + expect($task_count)->equals(1); + } + + function testItIgnoresCompletedAndPausedTasks() { + $this->createTask(WooCommerceSync::TASK_TYPE, ScheduledTask::STATUS_PAUSED); + $this->createTask(WooCommerceSync::TASK_TYPE, ScheduledTask::STATUS_COMPLETED); + $this->endpoint->setupWooCommerceInitialImport(); + $task_count = ScheduledTask::where('type', WooCommerceSync::TASK_TYPE)->count(); + expect($task_count)->equals(3); + } + + private function createTask($type, $status = null, $scheduled_at = null) { + if (!$scheduled_at) { + Carbon::createFromTimestamp(current_time('timestamp')); + } + $task = ScheduledTask::create(); + $task->type = $type; + $task->status = $status; + $task->scheduled_at = $scheduled_at; + $task->save(); + return $task; + } +} diff --git a/views/woocommerce_list_import.html b/views/woocommerce_list_import.html index 656b6ede0b..d9cc5274d5 100644 --- a/views/woocommerce_list_import.html +++ b/views/woocommerce_list_import.html @@ -12,6 +12,13 @@ <% block translations %> <%= localize({ -'wooCommerceListImportTitle': __('WooCommerce customers now have their own list'), +'wooCommerceListImportTitle': _x('WooCommerce customers now have their own list', 'Title on the customers import page'), +'wooCommerceListImportInfo1': __('MailPoet will create a list of your WooCommerce customers, even those who don’t have an account, known as "Guests".'), +'wooCommerceListImportInfo2': __('New customers will be able to join this list during checkout. You can manage this new checkout feature in your MailPoet Settings.'), +'wooCommerceListImportInfo3': __('To begin, please choose how you want to populate your list:'), +'wooCommerceListImportCheckboxSubscribed': __('add and subscribe all my customers to this list because they agreed to receive marketing emails from me'), +'wooCommerceListImportCheckboxUnsubscribed': __('add all my customers to the list, but as unsubscribed. They can join this list next time they check out'), +'wooCommerceListImportInfo4': __('Their subscription preference on other lists won’t be changed.'), +'wooCommerceListImportSubmit': _x('Create my WooCommerce Customers list now!', 'Submit button caption'), }) %> <% endblock %>