Implemented basic support for GDPR

This commit is contained in:
Tomas Bures 2018-11-22 00:02:14 +03:00
parent 9f9cbc4c2b
commit 92ca1c0f28
21 changed files with 271 additions and 105 deletions

View file

@ -69,7 +69,8 @@ export default class CUD extends Component {
'web_unsubscribed_notice',
'mail_unsubscription_confirmed_html',
'mail_unsubscription_confirmed_text',
'web_manual_unsubscribe_notice'
'web_manual_unsubscribe_notice',
'web_privacy_policy_notice'
];
this.initForm({
@ -206,6 +207,11 @@ export default class CUD extends Component {
label: t('webManualUnsubscribeNotice'),
mode: 'html',
help: helpMjmlGeneral
},
web_privacy_policy_notice: {
label: t('Privacy policy'),
mode: 'html',
help: helpMjmlGeneral
}
};
@ -254,6 +260,12 @@ export default class CUD extends Component {
'web_manual_unsubscribe_notice'
]
},
gdpr: {
label: t('Data protection'),
options: [
'web_privacy_policy_notice'
]
},
};
}

View file

@ -39,6 +39,8 @@ import axios from "../../lib/axios";
import {getUrl} from "../../lib/urls";
import listStyles from "../styles.scss";
import styles from "../../lib/styles.scss";
import interoperableErrors
from "../../../../shared/interoperable-errors";
function truncate(str, len, ending = '...') {
@ -185,6 +187,10 @@ export default class CUD extends Component {
}
async submitHandler() {
await this.save();
}
async save(runAfterSave) {
const t = this.props.t;
const isEdit = !!this.props.entity;
@ -275,6 +281,16 @@ export default class CUD extends Component {
if (!isEdit) {
this.navigateTo(`/lists/${this.props.list.id}/imports/${submitResponse}/edit`);
} else {
try {
await axios.post(getUrl(`rest/import-start/${this.props.list.id}/${this.props.entity.id}`));
} catch (err) {
if (err instanceof interoperableErrors.InvalidStateError) {
// Just mask the fact that it's not possible to start anything and refresh instead.
} else {
throw err;
}
}
this.navigateToWithFlashMessage(`/lists/${this.props.list.id}/imports/${this.props.entity.id}/status`, 'success', t('importSaved'));
}
@ -389,11 +405,12 @@ export default class CUD extends Component {
}
}
let saveButtonLabel;
const saveButtons = []
if (!isEdit) {
saveButtonLabel = t('saveAndEditSettings');
saveButtons.push(<Button key="default" type="submit" className="btn-primary" icon="ok" label={t('saveAndEditSettings')}/>);
} else {
saveButtonLabel = t('save');
saveButtons.push(<Button key="default" type="submit" className="btn-primary" icon="ok" label={t('save')}/>);
saveButtons.push(<Button key="saveAndRun" className="btn-primary" icon="ok" label={t('Save and Run')} onClickAsync={async () => await this.save(true)}/>);
}
return (
@ -427,7 +444,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={saveButtonLabel}/>
{saveButtons}
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/${this.props.list.id}/imports/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -96,13 +96,13 @@ export function getRuleHelpers(t, fields) {
};
// FXIME - the localization here is still wrong
function getRelativeDateTreeLabel(rule, textFragment) {
function getRelativeDateTreeLabel(rule, variants) {
if (rule.value === 0) {
return t(/*ignore*/'Date in column ' + textFragment + ' the current date', {colName: ruleHelpers.getColumnName(rule.column)})
return t(variants[0], {colName: ruleHelpers.getColumnName(rule.column)})
} else if (rule.value > 0) {
return t(/*ignore*/'Date in column ' + textFragment + ' {{value}}-th day after the current date', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value});
return t(variants[1], {colName: ruleHelpers.getColumnName(rule.column), value: rule.value});
} else {
return t(/*ignore*/'Date in column ' + textFragment + ' {{value}}-th day before the current date', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value});
return t(variants[2], {colName: ruleHelpers.getColumnName(rule.column), value: rule.value});
}
}
@ -129,48 +129,23 @@ export function getRuleHelpers(t, fields) {
},
eqTodayPlusDays: {
dropdownLabel: t('onXthDayBeforeafterCurrentDate'),
/*
tMark('dateInColumnIsTheCurrentDate')
tMark('dateInColumnIsValuethDayAfterTheCurrent')
tMark('dateInColumnIsValuethDayBeforeTheCurrent')
*/
treeLabel: rule => getRelativeDateTreeLabel(rule, 'is'),
treeLabel: rule => getRelativeDateTreeLabel(rule, 'is', [tMark('dateInColumnIsTheCurrentDate'), tMark('dateInColumnIsValuethDayAfterTheCurrent'), tMark('dateInColumnIsValuethDayBeforeTheCurrent')]),
},
ltTodayPlusDays: {
dropdownLabel: t('beforeXthDayBeforeafterCurrentDate'),
/*
tMark('dateInColumnIsBeforeTheCurrentDate')
tMark('dateInColumnIsBeforeValuethDayAfterThe')
tMark('dateInColumnIsBeforeValuethDayBeforeThe')
*/
treeLabel: rule => getRelativeDateTreeLabel(rule, 'is before'),
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnIsBeforeTheCurrentDate'), tMark('dateInColumnIsBeforeValuethDayAfterThe'), tMark('dateInColumnIsBeforeValuethDayBeforeThe')]),
},
leTodayPlusDays: {
dropdownLabel: t('beforeOrOnXthDayBeforeafterCurrentDate'),
/*
tMark('dateInColumnIsBeforeOrOnTheCurrentDate')
tMark('dateInColumnIsBeforeOrOnValuethDayAfter')
tMark('dateInColumnIsBeforeOrOnValuethDayBefore')
*/
treeLabel: rule => getRelativeDateTreeLabel(rule, 'is before or on'),
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnIsBeforeOrOnTheCurrentDate'), tMark('dateInColumnIsBeforeOrOnValuethDayAfter'), tMark('dateInColumnIsBeforeOrOnValuethDayBefore')]),
},
gtTodayPlusDays: {
dropdownLabel: t('afterXthDayBeforeafterCurrentDate'),
/*
tMark('dateInColumnIsAfterTheCurrentDate')
tMark('dateInColumnIsAfterValuethDayAfterThe')
tMark('dateInColumnIsAfterValuethDayAfterThe')
*/
treeLabel: rule => getRelativeDateTreeLabel(rule, 'is after'),
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnIsAfterTheCurrentDate'), tMark('dateInColumnIsAfterValuethDayAfterThe'), tMark('dateInColumnIsAfterValuethDayAfterThe')]),
},
geTodayPlusDays: {
dropdownLabel: t('afterOrOnXthDayBeforeafterCurrentDate'),
/*
tMark('dateInColumnIsAfterOrOnTheCurrentDate')
tMark('dateInColumnIsAfterOrOnValuethDayAfter')
tMark('dateInColumnIsAfterOrOnValuethDayAfter')
*/
treeLabel: rule => getRelativeDateTreeLabel(rule, 'is after or on'),
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnIsAfterOrOnTheCurrentDate'), tMark('dateInColumnIsAfterOrOnValuethDayAfter'), tMark('dateInColumnIsAfterOrOnValuethDayAfter')]),
}
};

View file

@ -1,3 +1,7 @@
.mapping {
margin-top: 30px;
}
.erased {
color: #808080;
}

View file

@ -13,14 +13,18 @@ import {
withForm
} from '../../lib/form';
import {Icon, Button} from "../../lib/bootstrap-components";
import axios from '../../lib/axios';
import axios, {HTTPMethod} from '../../lib/axios';
import {getFieldTypes, getSubscriptionStatusLabels} from './helpers';
import {getUrl, getPublicUrl} from "../../lib/urls";
import {
DeleteModalDialog,
RestActionModalDialog,
tableDeleteDialogAddDeleteButton,
tableDeleteDialogInit,
tableDeleteDialogRender
} from "../../lib/modals";
import listStyles from "../styles.scss";
import styles from '../../lib/styles.scss';
@withTranslation()
@withForm
@ -87,7 +91,7 @@ export default class List extends Component {
const columns = [
{ data: 1, title: t('id'), render: data => <code>{data}</code> },
{ data: 2, title: t('email') },
{ data: 2, title: t('email'), render: data => data === null ? <span className={listStyles.erased}>{t('[ERASED]')}</span> : data },
{ data: 3, title: t('status'), render: (data, display, rowData) => this.subscriptionStatusLabels[data] + (rowData[5] ? ', ' + t('blacklisted') : '') },
{ data: 4, title: t('created'), render: data => data ? moment(data).fromNow() : '' }
];
@ -112,27 +116,30 @@ export default class List extends Component {
columns.push({
actions: data => {
const actions = [];
const id = data[0];
const email = data[2];
const status = data[3];
actions.push({
label: <Icon icon="edit" title={t('edit')}/>,
link: `/lists/${this.props.list.id}/subscriptions/${data[0]}/edit`
link: `/lists/${this.props.list.id}/subscriptions/${id}/edit`
});
if (data[3] === SubscriptionStatus.SUBSCRIBED) {
if (email && status === SubscriptionStatus.SUBSCRIBED) {
actions.push({
label: <Icon icon="off" title={t('unsubscribe')}/>,
action: () => this.unsubscribeSubscription(data[0])
action: () => this.unsubscribeSubscription(id)
});
}
if (!data[5]) {
if (email && !data[5]) {
actions.push({
label: <Icon icon="ban-circle" title={t('blacklist')}/>,
action: () => this.blacklistSubscription(data[2])
action: () => this.blacklistSubscription(email)
});
}
tableDeleteDialogAddDeleteButton(actions, this, null, data[0], data[2]);
tableDeleteDialogAddDeleteButton(actions, this, null, id, email);
return actions;
}

View file

@ -49,7 +49,11 @@ import axios
import {getUrl} from "./lib/urls";
import {langCodes} from "../../shared/langs";
const topLevelMenuKeys = ['lists', 'templates', 'campaigns', 'reports'];
const topLevelMenuKeys = ['lists', 'templates', 'campaigns'];
if (mailtrainConfig.reportsEnabmed) {
topLevelMenuKeys.push('reports');
}
@withTranslation()
class Root extends Component {