diff --git a/assets/js/src/subscribers/importExport/import.jsx b/assets/js/src/subscribers/importExport/import.jsx index f1dc93b438..af34a2cd6a 100644 --- a/assets/js/src/subscribers/importExport/import.jsx +++ b/assets/js/src/subscribers/importExport/import.jsx @@ -81,17 +81,7 @@ jQuery(document).ready(() => { ); } return; - // render process button for each method - const methodProcessContainerTemplate = Handlebars.compile(jQuery('#method_process_template').html()); - jQuery('.mailpoet_method_process').html(methodProcessContainerTemplate()); - // define reusable variables - const currentStepE = jQuery(window.location.hash); - const methodSelectionElement = jQuery('#select_method'); - const pasteInputElement = jQuery('#paste_input'); - const pasteInputPlaceholderElement = pasteInputElement.data('placeholder').replace(/\\n/g, '\n'); - const pasteProcessButtonElement = jQuery('#method_paste > div.mailpoet_method_process') - .find('a.mailpoet_process'); const mailChimpKeyInputElement = jQuery('#mailchimp_key'); const mailChimpKeyVerifyButtonElement = jQuery('#mailchimp_key_verify'); const mailChimpListsContainerElement = jQuery('#mailchimp_lists'); @@ -101,35 +91,6 @@ jQuery(document).ready(() => { const uploadProcessButtonElement = jQuery('#method_file > div.mailpoet_method_process') .find('a.mailpoet_process'); - // define method change behavior - methodSelectionElement.change(() => { - const availableMethods = jQuery(':radio[name="select_method"]'); - const selectedMethod = availableMethods.index(availableMethods.filter(':checked')); - MailPoet.Notice.hide(); - // hide all methods - currentStepE.find('.inside') - .children('div[id^="method_"]') - .hide(); - // show selected method - currentStepE.find('.inside') - .children(`div[id^="method_"]:eq(${selectedMethod})`) - .show() - .find('table') - .show(); - }); - - // start step 1 - showCurrentStep(); - - function toggleNextStepButton(element, condition) { - const disabled = 'button-disabled'; - if (condition === 'on') { - element.closest('table a').removeClass(disabled); - return; - } - element.closest('table a').addClass(disabled); - } - function papaParserConfig(isFile) { return { skipEmptyLines: true, @@ -191,47 +152,6 @@ jQuery(document).ready(() => { mailChimpListsContainerElement.show(); } - /* - * Paste - */ - pasteInputElement - .attr('value', pasteInputPlaceholderElement).css('color', '#999') - .focus((event) => { - if (jQuery(event.currentTarget).val() === pasteInputPlaceholderElement) { - jQuery(event.currentTarget).attr('value', '').css('color', '#222'); - } - }) - .blur((event) => { - if (jQuery(event.currentTarget).val() === '') { - jQuery(event.currentTarget).attr('value', pasteInputPlaceholderElement).css('color', '#999'); - } - toggleNextStepButton( - pasteProcessButtonElement, - (event.currentTarget.value.trim() !== '') ? 'on' : 'off' - ); - }) - .keyup((event) => { - toggleNextStepButton( - pasteProcessButtonElement, - (event.currentTarget.value.trim() !== '') ? 'on' : 'off' - ); - }); - - pasteProcessButtonElement.click(() => { - const pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1; - MailPoet.Notice.hide(); - // get an approximate size of textarea paste in bytes - if (pasteSize > window.maxPostSizeBytes) { - MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice')); - return; - } - // delay loading indicator for 10ms or else it's just too fast :) - MailPoet.Modal.loading(true); - setTimeout(() => { - Papa.parse(pasteInputElement.val(), papaParserConfig(false)); - }, 10); - }); - /* * CSV file */ diff --git a/assets/js/src/subscribers/importExport/import/step_method_selection.jsx b/assets/js/src/subscribers/importExport/import/step_method_selection.jsx index 4d9100c6ac..7720bbde7c 100644 --- a/assets/js/src/subscribers/importExport/import/step_method_selection.jsx +++ b/assets/js/src/subscribers/importExport/import/step_method_selection.jsx @@ -1,16 +1,35 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import MailPoet from 'mailpoet'; +import Papa from 'papaparse'; import PreviousNextStepButtons from './previous_next_step_buttons.jsx'; import SelectMethod from './step_method_selection/select_import_method.jsx'; import MethodPaste from './step_method_selection/method_paste.jsx'; import MethodUpload from './step_method_selection/method_upload.jsx'; import MethodMailChimp from './step_method_selection/method_mailchimp.jsx'; +import sanitizeCSVData from './sanitize_csv_data.jsx'; + +const SUBSCRIBERS_LIMIT_FOR_VALIDATION = 500; + +const getNextStepLink = (importData) => { + if (importData === undefined) { + return 'step_data_manipulation'; + } + if (importData.subscribersCount === undefined) { + return 'step_data_manipulation'; + } + if (importData.subscribersCount < SUBSCRIBERS_LIMIT_FOR_VALIDATION) { + return 'step_data_manipulation'; + } + return 'step_input_validation'; +}; function StepMethodSelection({ navigate, }) { const [canGoNext, setCanGoNext] = useState(false); const [method, setMethod] = useState(undefined); + const [csvData, setCsvData] = useState(''); const setInputValid = () => { setCanGoNext(true); @@ -20,13 +39,58 @@ function StepMethodSelection({ setCanGoNext(false); }; + function papaParserConfig(isFile) { + return { + skipEmptyLines: true, + error() { + MailPoet.Notice.hide(); + MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError')); + }, + complete(CSV) { + const sanitizedData = sanitizeCSVData(CSV.data); + if (sanitizedData) { + // since we assume that the header line is always present, we need + // to detect the header by checking if it contains a valid e-mail address + window.importData.step_method_selection = sanitizedData; + MailPoet.trackEvent('Subscribers import started', { + source: isFile ? 'file upload' : 'pasted data', + 'MailPoet Free version': window.mailpoet_version, + }); + navigate( + getNextStepLink(window.importData.step_method_selection), + { trigger: true } + ); + } else { + MailPoet.Modal.loading(false); + let errorNotice = MailPoet.I18n.t('noValidRecords'); + errorNotice = errorNotice.replace('[link]', MailPoet.I18n.t('csvKBLink')); + errorNotice = errorNotice.replace('[/link]', ''); + MailPoet.Notice.error(errorNotice); + } + }, + }; + } + + const process = () => { + const pasteSize = encodeURI(csvData).split(/%..|./).length - 1; + MailPoet.Notice.hide(); + // get an approximate size of textarea paste in bytes + if (pasteSize > window.maxPostSizeBytes) { + MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice')); + return; + } + // delay loading indicator for 10ms or else it's just too fast :) + MailPoet.Modal.loading(true); + Papa.parse(csvData, papaParserConfig(false)); + }; + const showNextButton = () => { if (method) { return ( navigate('step_data_manipulation', { trigger: true })} + onNextAction={process} /> ); } @@ -42,6 +106,7 @@ function StepMethodSelection({ { method === 'paste-method' ? ( diff --git a/assets/js/src/subscribers/importExport/import/step_method_selection/method_paste.jsx b/assets/js/src/subscribers/importExport/import/step_method_selection/method_paste.jsx index 3ce48c156e..d9c9a00b45 100644 --- a/assets/js/src/subscribers/importExport/import/step_method_selection/method_paste.jsx +++ b/assets/js/src/subscribers/importExport/import/step_method_selection/method_paste.jsx @@ -7,13 +7,14 @@ const kbLink = 'http://docs.mailpoet.com/article/126-importing-subscribers-with- const placeholder = 'Email, First Name, Last Name\njohn@doe.com, John, Doe\nmary@smith.com, Mary, Smith\njohnny@walker.com, Johnny, Walker'; -const MethodPaste = ({ setInputValid, setInputInvalid }) => { +const MethodPaste = ({ setInputValid, setInputInvalid, onValueChange }) => { const onChange = (e) => { if (e.target.value) { setInputValid(); } else { setInputInvalid(); } + onValueChange(e.target.value); }; return ( @@ -53,6 +54,7 @@ const MethodPaste = ({ setInputValid, setInputInvalid }) => { MethodPaste.propTypes = { setInputValid: PropTypes.func, setInputInvalid: PropTypes.func, + onValueChange: PropTypes.func.isRequired, }; MethodPaste.defaultProps = {