diff --git a/client/src/campaigns/CUD.js b/client/src/campaigns/CUD.js new file mode 100644 index 00000000..3216a3d4 --- /dev/null +++ b/client/src/campaigns/CUD.js @@ -0,0 +1,503 @@ +'use strict'; + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import { + Trans, + translate +} from 'react-i18next'; +import { + NavButton, + requiresAuthenticatedUser, + Title, + withPageHelpers +} from '../lib/page' +import { + ACEEditor, + AlignedRow, + Button, + ButtonRow, + CheckBox, + Dropdown, + Form, + FormSendMethod, + InputField, + StaticField, + TableSelect, + TextArea, + withForm +} from '../lib/form'; +import { + withAsyncErrorHandler, + withErrorHandling +} from '../lib/error-handling'; +import { + NamespaceSelect, + validateNamespace +} from '../lib/namespace'; +import {DeleteModalDialog} from "../lib/modals"; +import mailtrainConfig from 'mailtrainConfig'; +import { + getEditForm, + getTemplateTypes, + getTypeForm +} from '../templates/helpers'; +import {ActionLink} from "../lib/bootstrap-components"; +import axios from '../lib/axios'; +import styles from "../lib/styles.scss"; +import {getUrl} from "../lib/urls"; +import {CampaignType, CampaignSource} from "../../../shared/campaigns"; +import moment from 'moment'; +import {getMailerTypes} from "../send-configurations/helpers"; + + +@translate() +@withForm +@withPageHelpers +@withErrorHandling +@requiresAuthenticatedUser +export default class CUD extends Component { + constructor(props) { + super(props); + + this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_'); + this.mailerTypes = getMailerTypes(props.t); + + this.state = { + showMergeTagReference: false, + elementInFullscreen: false, + }; + + this.initForm({ + onChange: { + send_configuration: ::this.onSendConfigurationChanged + }, + onChangeBeforeValidation: { + data_sourceCustom_type: ::this.onCustomTemplateTypeChanged + } + }); + } + + static propTypes = { + action: PropTypes.string.isRequired, + wizard: PropTypes.string, + entity: PropTypes.object, + type: PropTypes.number.isRequired + } + + onCustomTemplateTypeChanged(mutState, key, oldType, type) { + if (type) { + this.templateTypes[type].afterTypeChange(mutState); + } + } + + componentDidMount() { + if (this.props.entity) { + this.getFormValuesFromEntity(this.props.entity, data => { + if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) { + data.data_sourceTemplate = data.data.sourceTemplate; + } else { + data.data_sourceTemplate = null; + } + + if (data.source === CampaignSource.CUSTOM || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) { + data.data_sourceCustom_type = data.data.source.type; + data.data_sourceCustom_data = data.data.source.data; + data.data_sourceCustom_html = data.data.source.html; + data.data_sourceCustom_text = data.data.source.text; + + this.templateTypes[data.type].afterLoad(data); + + } else { + data.data_sourceCustom_type = null; + data.data_sourceCustom_data = {}; + data.data_sourceCustom_html = ''; + data.data_sourceCustom_text = ''; + } + + if (data.source === CampaignSource.URL) { + data.data_sourceUrl = data.data.sourceUrl; + } else { + data.data_sourceUrl = null; + } + + if (data.type === CampaignType.RSS) { + data.data_feedUrl = data.data.feedUrl; + } else { + data.data_feedUrl = ''; + } + + data.useSegmentation = !!data.segment; + + this.fetchSendConfiguration(data.send_configuration); + }); + + } else { + this.populateFormValues({ + type: this.props.type, + + name: '', + description: '', + list: null, + segment: null, + useSegmentation: false, + send_configuration: null, + namespace: mailtrainConfig.user.namespace, + from_name_override: '', + from_name_overriden: false, + from_email_override: '', + from_email_overriden: false, + reply_to_override: '', + reply_to_overriden: false, + subject_override: '', + subject_overriden: false, + click_tracking_disabled: false, + open_trackings_disabled: false, + + source: CampaignSource.TEMPLATE, + + // This is for CampaignSource.TEMPLATE + data_sourceTemplate: null, + + // This is for CampaignSource.CUSTOM + data_sourceCustom_type: null, + data_sourceCustom_data: {}, + data_sourceCustom_html: '', + data_sourceCustom_text: '', + + ...this.templateTypes[mailtrainConfig.editors[0]].initData(), + + // This is for CampaignSource.URL + data_sourceUrl: '', + + // This is for CampaignType.RSS + data_feedUrl: '' + }); + } + } + + localValidateFormValues(state) { + const t = this.props.t; + + if (!state.getIn(['name', 'value'])) { + state.setIn(['name', 'error'], t('Name must not be empty')); + } else { + state.setIn(['name', 'error'], null); + } + + if (!state.getIn(['list', 'value'])) { + state.setIn(['list', 'error'], t('List must be selected')); + } else { + state.setIn(['list', 'error'], null); + } + + if (state.getIn(['useSegmentation', 'value']) && !state.getIn(['segment', 'value'])) { + state.setIn(['segment', 'error'], t('Segment must be selected')); + } else { + state.setIn(['segment', 'error'], null); + } + + if (state.getIn(['from_email_overriden', 'value']) && !state.getIn(['from_email_override', 'value'])) { + state.setIn(['from_email_override', 'error'], t('"From" email must not be empty')); + } else { + state.setIn(['from_email_override', 'error'], null); + } + + + const campaignTypeKey = state.getIn(['type', 'value']); + + const sourceTypeKey = state.getIn(['source', 'value']); + + if (sourceTypeKey === CampaignSource.TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) { + if (!state.getIn(['data_sourceTemplate', 'value'])) { + state.setIn(['data_sourceTemplate', 'error'], t('Template must be selected')); + } else { + state.setIn(['data_sourceTemplate', 'error'], null); + } + + } else if (sourceTypeKey === CampaignSource.CUSTOM) { + // The type is used only in create form. In case of CUSTOM_FROM_TEMPLATE, it is determined by the source template, so no need to check it here + const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']); + if (!customTemplateTypeKey) { + state.setIn(['data_sourceCustom_type', 'error'], t('Type must be selected')); + } else { + state.setIn(['data_sourceCustom_type', 'error'], null); + } + + if (customTemplateTypeKey) { + this.templateTypes[customTemplateTypeKey].validate(state); + } + + } else if (sourceTypeKey === CampaignSource.URL) { + if (!state.getIn(['data_sourceUrl', 'value'])) { + state.setIn(['data_sourceUrl', 'error'], t('URL must not be empty')); + } else { + state.setIn(['data_sourceUrl', 'error'], null); + } + } + + if (campaignTypeKey === CampaignType.RSS) { + if (!state.getIn(['data_feedUrl', 'value'])) { + state.setIn(['data_feedUrl', 'error'], t('RSS feed URL must be given')); + } else { + state.setIn(['data_feedUrl', 'error'], null); + } + } + + validateNamespace(t, state); + + } + + async submitHandler() { + const t = this.props.t; + + if (this.props.entity) { + const sourceTypeKey = this.getFormValue('source'); + if (sourceTypeKey === CampaignSource.CUSTOM || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) { + const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type'); + await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this); + } + } + + let sendMethod, url; + if (this.props.entity) { + sendMethod = FormSendMethod.PUT; + url = `rest/campaigns/${this.props.entity.id}` + } else { + sendMethod = FormSendMethod.POST; + url = 'rest/campaigns' + } + + this.disableForm(); + this.setFormStatusMessage('info', t('Saving ...')); + + const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => { + if (!data.useSegmentation) { + data.segment = null; + } + delete data.useSegmentation; + + data.data = {}; + if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) { + data.data.sourceTemplate = data.data_sourceTemplate; + } + + if (data.source === CampaignSource.CUSTOM || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) { + this.templateTypes[data.data_sourceCustom_type].beforeSave(data); + + data.data.source = { + type: data.data_sourceCustom_type, + data: data.data_sourceCustom_data, + html: data.data_sourceCustom_html, + text: data.data_sourceCustom_text + } + } + + if (data.source === CampaignSource.URL) { + data.data.sourceUrl = data.data_sourceUrl; + } + + if (data.type === CampaignType.RSS) { + data.data.feedUrl = data.data_feedUrl; + } + + for (const key in data) { + if (key.startsWith('data_')) { + delete data[key]; + } + } + }); + + if (submitResponse) { + if (this.props.entity) { + this.navigateToWithFlashMessage('/campaigns', 'success', t('Campaign saved')); + } else { + this.navigateToWithFlashMessage(`/campaigns/${submitResponse}/edit`, 'success', t('Campaign saved')); + } + } else { + this.enableForm(); + this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.')); + } + } + + async extractPlainText() { + const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type'); + await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this); + + const html = this.getFormValue('data_sourceCustom_html'); + if (!html) { + return; + } + + if (this.isFormDisabled()) { + return; + } + + this.disableForm(); + + const response = await axios.post(getUrl('rest/html-to-text', { html })); + + this.updateFormValue('data_sourceCustom_text', response.data.text); + + this.enableForm(); + } + + async toggleMergeTagReference() { + this.setState({ + showMergeTagReference: !this.state.showMergeTagReference + }); + } + + async setElementInFullscreen(elementInFullscreen) { + this.setState({ + elementInFullscreen + }); + } + + render() { + const t = this.props.t; + const isEdit = !!this.props.entity; + const canDelete = isEdit && this.props.entity.permissions.includes('delete'); + const useSaveAndEditLabel = !isEdit; + + let templateEdit = null; + let extraSettings = null; + + const sourceTypeKey = this.getFormValue('source'); + const campaignTypeKey = this.getFormValue('type'); + + if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) { + const templatesColumns = [ + { data: 1, title: t('Name') }, + { data: 2, title: t('Description') }, + { data: 3, title: t('Type'), render: data => this.templateTypes[data].typeName }, + { data: 4, title: t('Created'), render: data => moment(data).fromNow() }, + { data: 5, title: t('Namespace') }, + ]; + + templateEdit = ; + + } else if (sourceTypeKey === CampaignSource.CUSTOM || (isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) { + const customTemplateTypeOptions = []; + for (const key of mailtrainConfig.editors) { + customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName}); + } + + // TODO: Toggle HTML preview + + const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type'); + + let customTemplateEditForm = null; + let customTemplateTypeForm = null; + + if (customTemplateTypeKey) { + customTemplateTypeForm = getTypeForm(this, customTemplateTypeKey, isEdit); + + if (isEdit) { + customTemplateEditForm = getEditForm(this, customTemplateTypeKey); + } + } + + templateEdit =
+ {isEdit + ? + + {customTemplateTypeKey && this.templateTypes[customTemplateTypeKey].typeName} + + : + + } + + {customTemplateTypeForm} + + {customTemplateEditForm} +
; + + } else if (sourceTypeKey === CampaignSource.URL) { + templateEdit = + } + + if (campaignTypeKey === CampaignType.RSS) { + extraSettings = + + } + + const listsColumns = [ + { data: 1, title: t('Name') }, + { data: 2, title: t('ID'), render: data => {data} }, + { data: 3, title: t('Subscribers') }, + { data: 4, title: t('Description') }, + { data: 5, title: t('Namespace') } + ]; + + const segmentsColumns = [ + { data: 1, title: t('Name') } + ]; + + const sendConfigurationsColumns = [ + { data: 1, title: t('Name') }, + { data: 2, title: t('Description') }, + { data: 3, title: t('Type'), render: data => this.mailerTypes[data].typeName }, + { data: 4, title: t('Created'), render: data => moment(data).fromNow() }, + { data: 5, title: t('Namespace') } + ]; + + return ( +
+ {canDelete && + + } + + {isEdit ? t('Edit Campaign') : t('Create Campaign')} + +
+ +