Upload csv by file in React
[MAILPOET-1808]
This commit is contained in:
@@ -87,41 +87,6 @@ jQuery(document).ready(() => {
|
|||||||
const mailChimpListsContainerElement = jQuery('#mailchimp_lists');
|
const mailChimpListsContainerElement = jQuery('#mailchimp_lists');
|
||||||
const mailChimpProcessButtonElement = jQuery('#method_mailchimp > div.mailpoet_method_process')
|
const mailChimpProcessButtonElement = jQuery('#method_mailchimp > div.mailpoet_method_process')
|
||||||
.find('a.mailpoet_process');
|
.find('a.mailpoet_process');
|
||||||
const uploadElement = jQuery('#file_local');
|
|
||||||
const uploadProcessButtonElement = jQuery('#method_file > div.mailpoet_method_process')
|
|
||||||
.find('a.mailpoet_process');
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
router.navigate(
|
|
||||||
getMethodSelectionNextStepLink(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]', '</a>');
|
|
||||||
MailPoet.Notice.error(errorNotice);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayMailChimpLists(data) {
|
function displayMailChimpLists(data) {
|
||||||
const listSelectElement = mailChimpListsContainerElement.find('select');
|
const listSelectElement = mailChimpListsContainerElement.find('select');
|
||||||
@@ -152,35 +117,6 @@ jQuery(document).ready(() => {
|
|||||||
mailChimpListsContainerElement.show();
|
mailChimpListsContainerElement.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* CSV file
|
|
||||||
*/
|
|
||||||
uploadElement.change((event) => {
|
|
||||||
const ext = event.currentTarget.value.match(/[^.]+$/);
|
|
||||||
MailPoet.Notice.hide();
|
|
||||||
if (ext === null || ext[0].toLowerCase() !== 'csv') {
|
|
||||||
event.currentTarget.value.val('');
|
|
||||||
MailPoet.Notice.error(MailPoet.I18n.t('wrongFileFormat'));
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleNextStepButton(
|
|
||||||
uploadProcessButtonElement,
|
|
||||||
(event.currentTarget.value.trim() !== '') ? 'on' : 'off'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadProcessButtonElement.click(() => {
|
|
||||||
if (uploadElement.val().trim() !== '') {
|
|
||||||
// delay loading indicator for 10ms or else it's just too fast :)
|
|
||||||
MailPoet.Modal.loading(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
uploadElement.parse({
|
|
||||||
config: papaParserConfig(true),
|
|
||||||
});
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MailChimp
|
* MailChimp
|
||||||
*/
|
*/
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import Papa from 'papaparse';
|
|
||||||
import PreviousNextStepButtons from './previous_next_step_buttons.jsx';
|
import PreviousNextStepButtons from './previous_next_step_buttons.jsx';
|
||||||
import SelectMethod from './step_method_selection/select_import_method.jsx';
|
import SelectMethod from './step_method_selection/select_import_method.jsx';
|
||||||
import MethodPaste from './step_method_selection/method_paste.jsx';
|
import MethodPaste from './step_method_selection/method_paste.jsx';
|
||||||
import MethodUpload from './step_method_selection/method_upload.jsx';
|
import MethodUpload from './step_method_selection/method_upload.jsx';
|
||||||
import MethodMailChimp from './step_method_selection/method_mailchimp.jsx';
|
import MethodMailChimp from './step_method_selection/method_mailchimp.jsx';
|
||||||
import sanitizeCSVData from './sanitize_csv_data.jsx';
|
import processCsv from './step_method_selection/process_csv.jsx';
|
||||||
|
|
||||||
const SUBSCRIBERS_LIMIT_FOR_VALIDATION = 500;
|
const SUBSCRIBERS_LIMIT_FOR_VALIDATION = 500;
|
||||||
|
|
||||||
@@ -39,49 +38,18 @@ function StepMethodSelection({
|
|||||||
setCanGoNext(false);
|
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]', '</a>');
|
|
||||||
MailPoet.Notice.error(errorNotice);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const process = () => {
|
const process = () => {
|
||||||
const pasteSize = encodeURI(csvData).split(/%..|./).length - 1;
|
processCsv(csvData, (sanitizedData) => {
|
||||||
MailPoet.Notice.hide();
|
window.importData.step_method_selection = sanitizedData;
|
||||||
// get an approximate size of textarea paste in bytes
|
MailPoet.trackEvent('Subscribers import started', {
|
||||||
if (pasteSize > window.maxPostSizeBytes) {
|
source: method === 'file-method' ? 'file upload' : 'pasted data',
|
||||||
MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice'));
|
'MailPoet Free version': window.mailpoet_version,
|
||||||
return;
|
});
|
||||||
}
|
navigate(
|
||||||
// delay loading indicator for 10ms or else it's just too fast :)
|
getNextStepLink(window.importData.step_method_selection),
|
||||||
MailPoet.Modal.loading(true);
|
{ trigger: true }
|
||||||
Papa.parse(csvData, papaParserConfig(false));
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showNextButton = () => {
|
const showNextButton = () => {
|
||||||
@@ -112,10 +80,12 @@ function StepMethodSelection({
|
|||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{ method === 'csv-method'
|
{ method === 'file-method'
|
||||||
? (
|
? (
|
||||||
<MethodUpload
|
<MethodUpload
|
||||||
|
onValueChange={setCsvData}
|
||||||
setInputValid={setInputValid}
|
setInputValid={setInputValid}
|
||||||
|
setInputInvalid={setInputInValid}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +1,69 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import ReactStringReplace from 'react-string-replace';
|
||||||
|
|
||||||
|
const kbLink = 'http://docs.mailpoet.com/article/126-importing-subscribers-with-csv-files'
|
||||||
|
|
||||||
|
const MethodUpload = ({ setInputValid, setInputInvalid, onValueChange }) => {
|
||||||
|
const onChange = (e) => {
|
||||||
|
const ext = e.target.value.match(/[^.]+$/);
|
||||||
|
MailPoet.Notice.hide();
|
||||||
|
if (ext === null || ext[0].toLowerCase() !== 'csv') {
|
||||||
|
setInputInvalid();
|
||||||
|
MailPoet.Notice.error(MailPoet.I18n.t('wrongFileFormat'));
|
||||||
|
onValueChange('');
|
||||||
|
} else {
|
||||||
|
onValueChange(e.target.files[0]);
|
||||||
|
setInputValid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const MethodUpload = ({ setInputValid }) => {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
MethodUpload
|
<>
|
||||||
|
<label htmlFor="paste_input" className="import_method_paste">
|
||||||
|
<div className="import_paste_texts">
|
||||||
|
<span className="import_heading">{MailPoet.I18n.t('methodUpload')}</span>
|
||||||
|
<p className="description">
|
||||||
|
{ReactStringReplace(
|
||||||
|
MailPoet.I18n.t('pasteDescription'),
|
||||||
|
/\[link\](.*?)\[\/link\]/,
|
||||||
|
match => (
|
||||||
|
<a
|
||||||
|
href={`${kbLink}`}
|
||||||
|
key="kb-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{ match }
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="file_local"
|
||||||
|
accept=".csv"
|
||||||
|
data-automation-id="import-file-upload-input"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MethodUpload.propTypes = {
|
MethodUpload.propTypes = {
|
||||||
setInputValid: PropTypes.func,
|
setInputValid: PropTypes.func,
|
||||||
|
setInputInvalid: PropTypes.func,
|
||||||
|
onValueChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
MethodUpload.defaultProps = {
|
MethodUpload.defaultProps = {
|
||||||
setInputValid: () => {},
|
setInputValid: () => {},
|
||||||
|
setInputInvalid: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MethodUpload;
|
export default MethodUpload;
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
import Papa from 'papaparse';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import sanitizeCSVData from '../sanitize_csv_data.jsx';
|
||||||
|
|
||||||
|
function papaParserConfig(done) {
|
||||||
|
return {
|
||||||
|
skipEmptyLines: true,
|
||||||
|
error() {
|
||||||
|
MailPoet.Notice.hide();
|
||||||
|
MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError'));
|
||||||
|
},
|
||||||
|
complete(CSV) {
|
||||||
|
const sanitizedData = sanitizeCSVData(CSV.data);
|
||||||
|
if (sanitizedData) {
|
||||||
|
done(sanitizedData);
|
||||||
|
} else {
|
||||||
|
MailPoet.Modal.loading(false);
|
||||||
|
let errorNotice = MailPoet.I18n.t('noValidRecords');
|
||||||
|
errorNotice = errorNotice.replace('[link]', MailPoet.I18n.t('csvKBLink'));
|
||||||
|
errorNotice = errorNotice.replace('[/link]', '</a>');
|
||||||
|
MailPoet.Notice.error(errorNotice);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const process = (csvData, done) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
MailPoet.Modal.loading(true);
|
||||||
|
Papa.parse(csvData, papaParserConfig(done));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default process;
|
@@ -28,8 +28,8 @@ function SelectImportMethod({
|
|||||||
name="select_method"
|
name="select_method"
|
||||||
data-automation-id="import-csv-method"
|
data-automation-id="import-csv-method"
|
||||||
id="import-csv-method"
|
id="import-csv-method"
|
||||||
checked={activeMethod === 'csv-method'}
|
checked={activeMethod === 'file-method'}
|
||||||
onChange={() => onMethodChange('csv-method')}
|
onChange={() => onMethodChange('file-method')}
|
||||||
/>
|
/>
|
||||||
{MailPoet.I18n.t('methodUpload')}
|
{MailPoet.I18n.t('methodUpload')}
|
||||||
</label>
|
</label>
|
||||||
|
@@ -69,7 +69,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th style="width: 300px;">
|
||||||
<a href="javascript:;" id="return_to_previous"
|
<a href="javascript:;" id="return_to_previous"
|
||||||
class="button-primary wysija button"><%= __('Previous step') %> </a>
|
class="button-primary wysija button"><%= __('Previous step') %> </a>
|
||||||
|
|
||||||
|
@@ -1,29 +1,5 @@
|
|||||||
<div id="step_method_selection" class="mailpoet_hidden method_selection_step">
|
<div id="step_method_selection" class="mailpoet_hidden method_selection_step">
|
||||||
<div class="inside">
|
<div class="inside">
|
||||||
<!-- CSV file -->
|
|
||||||
<div id="method_file" class="mailpoet_hidden">
|
|
||||||
<table class="mailpoet_subscribers form-table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">
|
|
||||||
<label for="file_local">
|
|
||||||
<%= __('Upload a file') %>
|
|
||||||
<p class="description">
|
|
||||||
<%= csvDescription|replaceLinkTags(csvKBLink, {'target' : '_blank'})|raw %>
|
|
||||||
</p>
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<input type="file" id="file_local" accept=".csv" data-automation-id="import-file-upload-input" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="mailpoet_method_process">
|
|
||||||
<!-- Template data -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mailchimp -->
|
<!-- Mailchimp -->
|
||||||
<div id="method_mailchimp" class="mailpoet_hidden">
|
<div id="method_mailchimp" class="mailpoet_hidden">
|
||||||
<table class="mailpoet_subscribers form-table">
|
<table class="mailpoet_subscribers form-table">
|
||||||
|
Reference in New Issue
Block a user