diff --git a/client/src/lists/segments/CUD.js b/client/src/lists/segments/CUD.js index 5c9765f0..d096ec42 100644 --- a/client/src/lists/segments/CUD.js +++ b/client/src/lists/segments/CUD.js @@ -3,18 +3,35 @@ import React, {Component} from "react"; import PropTypes from "prop-types"; import {translate} from "react-i18next"; -import {NavButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from "../../lib/page"; -import {Button as FormButton, ButtonRow, Dropdown, Form, FormSendMethod, InputField, withForm} from "../../lib/form"; -import {withAsyncErrorHandler, withErrorHandling} from "../../lib/error-handling"; +import { + NavButton, + requiresAuthenticatedUser, + Title, + Toolbar, + withPageHelpers +} from "../../lib/page"; +import { + Button as FormButton, + ButtonRow, + Dropdown, + Form, + FormSendMethod, + InputField, + withForm +} from "../../lib/form"; +import {withErrorHandling} from "../../lib/error-handling"; import {DeleteModalDialog} from "../../lib/modals"; -import interoperableErrors from "../../../../shared/interoperable-errors"; import styles from "./CUD.scss"; import {DragDropContext} from "react-dnd"; import HTML5Backend from "react-dnd-html5-backend"; import TouchBackend from "react-dnd-touch-backend"; import SortableTree from "react-sortable-tree"; -import {ActionLink, Button, Icon} from "../../lib/bootstrap-components"; +import { + ActionLink, + Button, + Icon +} from "../../lib/bootstrap-components"; import {getRuleHelpers} from "./helpers"; import RuleSettingsPane from "./RuleSettingsPane"; @@ -88,18 +105,6 @@ export default class CUD extends Component { return tree; } - @withAsyncErrorHandler - async loadFormValues() { - await this.getFormValuesFromURL(`/rest/segments/${this.props.list.id}/${this.props.entity.id}`, data => { - data.rootRuleType = data.settings.rootRule.type; - data.selectedRule = null; // Validation errors of the selected rule are attached to this which makes sure we don't submit the segment if the opened rule has errors - - this.setState({ - rulesTree: this.getTreeFromRules(data.settings.rootRule.rules) - }); - }); - } - componentDidMount() { if (this.props.entity) { this.setState({ @@ -167,7 +172,14 @@ export default class CUD extends Component { if (submitSuccessful) { if (stay) { - await this.loadFormValues(); + await this.getFormValuesFromURL(`/rest/segments/${this.props.list.id}/${this.props.entity.id}`, data => { + data.rootRuleType = data.settings.rootRule.type; + data.selectedRule = null; // Validation errors of the selected rule are attached to this which makes sure we don't submit the segment if the opened rule has errors + + this.setState({ + rulesTree: this.getTreeFromRules(data.settings.rootRule.rules) + }); + }); this.enableForm(); this.setFormStatusMessage('success', t('Segment saved')); } else { diff --git a/client/src/reports/templates/CUD.js b/client/src/reports/templates/CUD.js index b2679c3f..7abc018a 100644 --- a/client/src/reports/templates/CUD.js +++ b/client/src/reports/templates/CUD.js @@ -1,12 +1,33 @@ 'use strict'; -import React, { Component } from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; -import { translate, Trans } from 'react-i18next'; -import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../../lib/page' -import { withForm, Form, FormSendMethod, InputField, TextArea, Dropdown, ACEEditor, ButtonRow, Button } from '../../lib/form'; -import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling'; -import { validateNamespace, NamespaceSelect } from '../../lib/namespace'; +import { + Trans, + translate +} from 'react-i18next'; +import { + NavButton, + requiresAuthenticatedUser, + Title, + withPageHelpers +} from '../../lib/page' +import { + ACEEditor, + Button, + ButtonRow, + Dropdown, + Form, + FormSendMethod, + InputField, + TextArea, + withForm +} from '../../lib/form'; +import {withErrorHandling} from '../../lib/error-handling'; +import { + NamespaceSelect, + validateNamespace +} from '../../lib/namespace'; import {DeleteModalDialog} from "../../lib/modals"; import mailtrainConfig from 'mailtrainConfig'; import 'brace/mode/javascript'; @@ -33,11 +54,6 @@ export default class CUD extends Component { entity: PropTypes.object } - @withAsyncErrorHandler - async loadFormValues() { - await this.getFormValuesFromURL(`/rest/report-templates/${this.props.entity.id}`); - } - componentDidMount() { if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); @@ -259,7 +275,7 @@ export default class CUD extends Component { if (submitSuccessful) { if (stay) { - await this.loadFormValues(); + await this.getFormValuesFromURL(`/rest/report-templates/${this.props.entity.id}`); this.enableForm(); this.setFormStatusMessage('success', t('Report template saved')); } else { diff --git a/client/src/send-configurations/CUD.js b/client/src/send-configurations/CUD.js index b06ee8f8..a02cbc69 100644 --- a/client/src/send-configurations/CUD.js +++ b/client/src/send-configurations/CUD.js @@ -99,13 +99,15 @@ export default class CUD extends Component { localValidateFormValues(state) { const t = this.props.t; + const typeKey = state.getIn(['mailer_type', 'value']); + if (!state.getIn(['name', 'value'])) { state.setIn(['name', 'error'], t('Name must not be empty')); } else { state.setIn(['name', 'error'], null); } - if (!state.getIn(['mailer_type', 'value'])) { + if (!typeKey) { state.setIn(['mailer_type', 'error'], t('Mailer type must be selected')); } else { state.setIn(['mailer_type', 'error'], null); @@ -117,8 +119,11 @@ export default class CUD extends Component { state.setIn(['verp_hostname', 'error'], null); } - validateNamespace(t, state); + + if (typeKey) { + this.mailerTypes[typeKey].validate(state); + } } async submitHandler() { @@ -157,7 +162,6 @@ export default class CUD extends Component { const canDelete = isEdit && this.props.entity.permissions.includes('delete') && this.props.entity.id !== getSystemSendConfigurationId(); const typeKey = this.getFormValue('mailer_type'); - console.log(typeKey); let mailerForm = null; if (typeKey) { mailerForm = this.mailerTypes[typeKey].getForm(this); @@ -210,12 +214,6 @@ export default class CUD extends Component { : VERP bounce handling server is not enabled. Modify your server configuration file and restart server to enable it. } - - - - - - diff --git a/client/src/send-configurations/helpers.js b/client/src/send-configurations/helpers.js index 0058a917..9004dd8d 100644 --- a/client/src/send-configurations/helpers.js +++ b/client/src/send-configurations/helpers.js @@ -18,65 +18,6 @@ export const mailerTypesOrder = [ MailerType.AWS_SES ]; -function getInitCommon() { - return { - maxConnections: '5', - throttling: '', - logTransactions: false - }; -} - -function getInitGenericSMTP() { - return { - ...getInitCommon(), - smtpHostname: '', - smtpPort: '', - smtpEncryption: 'NONE', - smtpUseAuth: false, - smtpUser: '', - smtpPassword: '', - smtpAllowSelfSigned: false, - smtpMaxMessages: '100' - }; -} - -function afterLoadCommon(data) { - data.maxConnections = data.mailer_settings.maxConnections; - data.throttling = data.mailer_settings.throttling || ''; - data.logTransaction = data.mailer_settings.logTransactions; -} - -function afterLoadGenericSMTP(data) { - afterLoadCommon(data); - data.smtpHostname = data.mailer_settings.hostname; - data.smtpPort = data.mailer_settings.port || ''; - data.smtpEncryption = data.mailer_settings.encryption; - data.smtpUseAuth = data.mailer_settings.useAuth; - data.smtpUser = data.mailer_settings.user; - data.smtpPassword = data.mailer_settings.password; - data.smtpAllowSelfSigned = data.mailer_settings.allowSelfSigned; - data.smtpMaxMessages = data.mailer_settings.maxMessages; -} - -function beforeSaveCommon(data) { - data.mailer_settings = {}; - data.mailer_settings.maxConnections = Number(data.maxConnections); - data.mailer_settings.throttling = Number(data.throttling); - data.mailer_settings.logTransactions = data.logTransaction; -} - -function beforeSaveGenericSMTP(data) { - beforeSaveCommon(data); - data.mailer_settings.hostname = data.smtpHostname; - data.mailer_settings.port = Number(data.smtpPort); - data.mailer_settings.encryption = data.smtpEncryption; - data.mailer_settings.useAuth = data.smtpUseAuth; - data.mailer_settings.user = data.smtpUser; - data.mailer_settings.password = data.smtpPassword; - data.mailer_settings.allowSelfSigned = data.smtpAllowSelfSigned; - data.mailer_settings.maxMessages = Number(data.smtpMaxMessages); -} - export function getMailerTypes(t) { const mailerTypes = {}; @@ -99,6 +40,88 @@ export function getMailerTypes(t) { } } + function validateNumber(state, field, label, emptyAllowed = false) { + const value = state.getIn([field, 'value']); + if (typeof value === 'string' && value.trim() === '' && !emptyAllowed) { // After load, the numerical values can be still numbers + state.setIn([field, 'error'], t(`${label} must not be empty`)); + } else if (isNaN(value)) { + state.setIn([field, 'error'], t(`${label} must be a number`)); + } else { + state.setIn([field, 'error'], null); + } + + } + + function getInitCommon() { + return { + maxConnections: '5', + throttling: '', + logTransactions: false + }; + } + + function getInitGenericSMTP() { + return { + ...getInitCommon(), + smtpHostname: '', + smtpPort: '', + smtpEncryption: 'NONE', + smtpUseAuth: false, + smtpUser: '', + smtpPassword: '', + smtpAllowSelfSigned: false, + smtpMaxMessages: '100' + }; + } + + function afterLoadCommon(data) { + data.maxConnections = data.mailer_settings.maxConnections; + data.throttling = data.mailer_settings.throttling || ''; + data.logTransactions = data.mailer_settings.logTransactions; + } + + function afterLoadGenericSMTP(data) { + afterLoadCommon(data); + data.smtpHostname = data.mailer_settings.hostname; + data.smtpPort = data.mailer_settings.port || ''; + data.smtpEncryption = data.mailer_settings.encryption; + data.smtpUseAuth = data.mailer_settings.useAuth; + data.smtpUser = data.mailer_settings.user; + data.smtpPassword = data.mailer_settings.password; + data.smtpAllowSelfSigned = data.mailer_settings.allowSelfSigned; + data.smtpMaxMessages = data.mailer_settings.maxMessages; + } + + function beforeSaveCommon(data) { + data.mailer_settings = {}; + data.mailer_settings.maxConnections = Number(data.maxConnections); + data.mailer_settings.throttling = Number(data.throttling); + data.mailer_settings.logTransactions = data.logTransactions; + } + + function beforeSaveGenericSMTP(data) { + beforeSaveCommon(data); + data.mailer_settings.hostname = data.smtpHostname; + data.mailer_settings.port = Number(data.smtpPort); + data.mailer_settings.encryption = data.smtpEncryption; + data.mailer_settings.useAuth = data.smtpUseAuth; + data.mailer_settings.user = data.smtpUser; + data.mailer_settings.password = data.smtpPassword; + data.mailer_settings.allowSelfSigned = data.smtpAllowSelfSigned; + data.mailer_settings.maxMessages = Number(data.smtpMaxMessages); + } + + function validateCommon(state) { + validateNumber(state, 'maxConnections', 'Max connections'); + validateNumber(state, 'throttling', 'Throttling', true); + } + + function validateGenericSMTP(state) { + validateCommon(state); + validateNumber(state, 'smtpPort', 'Port', true); + validateNumber(state, 'smtpMaxMessages', 'Max messages'); + } + const typeOptions = [ { key: MailerType.GENERIC_SMTP, label: t('Generic SMTP')}, { key: MailerType.ZONE_MTA, label: t('Zone MTA')}, @@ -153,6 +176,9 @@ export function getMailerTypes(t) { }, afterTypeChange: mutState => { initFieldsIfMissing(mutState, MailerType.GENERIC_SMTP); + }, + validate: state => { + validateGenericSMTP(state); } }; @@ -212,6 +238,9 @@ export function getMailerTypes(t) { }, afterTypeChange: mutState => { initFieldsIfMissing(mutState, MailerType.ZONE_MTA); + }, + validate: state => { + validateGenericSMTP(state); } }; @@ -251,6 +280,9 @@ export function getMailerTypes(t) { }, afterTypeChange: mutState => { initFieldsIfMissing(mutState, MailerType.AWS_SES); + }, + validate: state => { + validateCommon(state); } }; diff --git a/client/src/settings/Update.js b/client/src/settings/Update.js index 4b94f252..902ada98 100644 --- a/client/src/settings/Update.js +++ b/client/src/settings/Update.js @@ -1,29 +1,27 @@ 'use strict'; -import React, { Component } from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; -import { translate, Trans } from 'react-i18next'; -import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../lib/page'; import { - withForm, + Trans, + translate +} from 'react-i18next'; +import { + requiresAuthenticatedUser, + Title, + withPageHelpers +} from '../lib/page'; +import { + Button, + ButtonRow, + Fieldset, Form, FormSendMethod, InputField, TextArea, - TableSelect, - ButtonRow, - Button, - Dropdown, - StaticField, - CheckBox, - Fieldset + withForm } from '../lib/form'; -import { withErrorHandling } from '../lib/error-handling'; -import { DeleteModalDialog } from '../lib/modals'; -import { validateNamespace, NamespaceSelect } from '../lib/namespace'; -import { UnsubscriptionMode } from '../../../shared/lists'; -import styles from "../lib/styles.scss"; -import mailtrainConfig from 'mailtrainConfig'; +import {withErrorHandling} from '../lib/error-handling'; @translate() @withForm @@ -42,7 +40,7 @@ export default class Update extends Component { static propTypes = { entity: PropTypes.object } - + componentDidMount() { this.getFormValuesFromEntity(this.props.entity); } @@ -60,7 +58,9 @@ export default class Update extends Component { const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.PUT, '/rest/settings'); if (submitSuccessful) { - this.navigateToWithFlashMessage('/settings', 'success', t('Global settings saved')); + await this.getFormValuesFromURL('/rest/settings'); + this.enableForm(); + this.setFormStatusMessage('success', t('Global settings saved')); } else { this.enableForm(); this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.')); diff --git a/client/src/templates/CUD.js b/client/src/templates/CUD.js index 4d40d940..7f2799ef 100644 --- a/client/src/templates/CUD.js +++ b/client/src/templates/CUD.js @@ -52,11 +52,6 @@ export default class CUD extends Component { entity: PropTypes.object } - @withAsyncErrorHandler - async loadFormValues() { - await this.getFormValuesFromURL(`/rest/templates/${this.props.entity.id}`); - } - componentDidMount() { if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity); diff --git a/client/src/templates/mosaico/CUD.js b/client/src/templates/mosaico/CUD.js index cee1c8f7..55c1c033 100644 --- a/client/src/templates/mosaico/CUD.js +++ b/client/src/templates/mosaico/CUD.js @@ -1,16 +1,36 @@ 'use strict'; -import React, { Component } from 'react'; +import React, {Component} from 'react'; import PropTypes from 'prop-types'; -import { translate, Trans } from 'react-i18next'; -import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../../lib/page' -import { withForm, Form, FormSendMethod, InputField, TextArea, Dropdown, ButtonRow, Button } from '../../lib/form'; -import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling'; -import { validateNamespace, NamespaceSelect } from '../../lib/namespace'; +import {translate} from 'react-i18next'; +import { + NavButton, + requiresAuthenticatedUser, + Title, + withPageHelpers +} from '../../lib/page' +import { + Button, + ButtonRow, + Dropdown, + Form, + FormSendMethod, + InputField, + TextArea, + withForm +} from '../../lib/form'; +import {withErrorHandling} from '../../lib/error-handling'; +import { + NamespaceSelect, + validateNamespace +} from '../../lib/namespace'; import {DeleteModalDialog} from "../../lib/modals"; -import { versafix } from "../../../../shared/mosaico-templates"; -import { getTemplateTypes, getTemplateTypesOrder } from "./helpers"; +import {versafix} from "../../../../shared/mosaico-templates"; +import { + getTemplateTypes, + getTemplateTypesOrder +} from "./helpers"; @translate() @withForm @@ -42,13 +62,6 @@ export default class CUD extends Component { entity: PropTypes.object } - @withAsyncErrorHandler - async loadFormValues() { - await this.getFormValuesFromURL(`/rest/mosaico-templates/${this.props.entity.id}`, data => { - this.templateTypes[data.type].afterLoad(data); - }); - } - componentDidMount() { if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity, data => { @@ -126,7 +139,9 @@ export default class CUD extends Component { if (submitSuccessful) { if (stay) { - await this.loadFormValues(); + await this.getFormValuesFromURL(`/rest/mosaico-templates/${this.props.entity.id}`, data => { + this.templateTypes[data.type].afterLoad(data); + }); this.enableForm(); this.setFormStatusMessage('success', t('Mosaico template saved')); } else { diff --git a/models/send-configuration.js b/models/send-configuration.js index 63be20b8..03014ab3 100644 --- a/models/send-configuration.js +++ b/models/send-configuration.js @@ -11,12 +11,12 @@ const {MailerType, getSystemSendConfigurationId} = require('../shared/send-confi const allowedKeys = new Set(['name', 'description', 'from_email', 'from_email_overridable', 'from_name', 'from_name_overridable', 'subject', 'subject_overridable', 'verp_hostname', 'mailer_type', 'mailer_settings', 'namespace']); +const allowedMailerTypes = new Set(Object.values(MailerType)); function hash(entity) { return hasher.hash(filterObject(entity, allowedKeys)); } - async function listDTAjax(context, params) { return await dtHelpers.ajaxListWithPermissions( context, @@ -47,7 +47,7 @@ async function getById(context, id, withPermissions = true) { async function _validateAndPreprocess(tx, entity, isCreate) { await namespaceHelpers.validateEntity(tx, entity); - enforce(entity.mailer_type >= 0 && entity.mailer_type < MailerType.MAX, 'Unknown mailer type'); + enforce(allowedMailerTypes.has(entity.mailer_type), 'Unknown mailer type'); entity.mailer_settings = JSON.stringify(entity.mailer_settings); } diff --git a/shared/send-configurations.js b/shared/send-configurations.js index de6b8ca8..fa044ac5 100644 --- a/shared/send-configurations.js +++ b/shared/send-configurations.js @@ -3,8 +3,7 @@ const MailerType = { GENERIC_SMTP: 'generic_smtp', ZONE_MTA: 'zone_mta', - AWS_SES: 'aws_ses', - MAX: 3 + AWS_SES: 'aws_ses' }; function getSystemSendConfigurationId() {
VERP bounce handling server is not enabled. Modify your server configuration file and restart server to enable it.