Blacklist functionality

Some API improvements
This commit is contained in:
Tomas Bures 2017-09-17 16:36:23 +02:00
parent c343e4efd3
commit 9203b5cee7
40 changed files with 726 additions and 398 deletions

View file

@ -13,6 +13,7 @@ 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';
@translate()
@withForm
@ -46,7 +47,7 @@ export default class CUD extends Component {
default_form: null,
public_subscribe: true,
unsubscription_mode: UnsubscriptionMode.ONE_STEP,
namespace: null
namespace: mailtrainConfig.user.namespace
});
}
}
@ -102,6 +103,7 @@ export default class CUD extends Component {
render() {
const t = this.props.t;
const isEdit = !!this.props.entity;
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
const unsubcriptionModeOptions = [
{
@ -146,7 +148,7 @@ export default class CUD extends Component {
return (
<div>
{isEdit &&
{canDelete &&
<DeleteModalDialog
stateOwner={this}
visible={this.props.action === 'delete'}
@ -185,7 +187,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save')}/>
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/lists/${this.props.entity.id}/delete`}/>}
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/lists/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>
</div>

View file

@ -16,6 +16,8 @@ import validators from '../../../../shared/validators';
import slugify from 'slugify';
import { parseDate, parseBirthday, DateFormat } from '../../../../shared/date';
import styles from "../../lib/styles.scss";
import 'brace/mode/json';
import 'brace/mode/handlebars';
@translate()
@withForm
@ -113,10 +115,7 @@ export default class CUD extends Component {
dateFormat: 'eur',
orderListBefore: 'end', // possible values are <numeric id> / 'end' / 'none'
orderSubscribeBefore: 'end',
orderManageBefore: 'end',
orderListOptions: [],
orderSubscribeOptions: [],
orderManageOptions: []
orderManageBefore: 'end'
});
}
}
@ -282,16 +281,6 @@ export default class CUD extends Component {
this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.'));
}
} catch (error) {
if (error instanceof interoperableErrors.DependencyNotFoundError) {
this.setFormStatusMessage('danger',
<span>
<strong>{t('Your updates cannot be saved.')}</strong>{' '}
{t('It seems that another field upon which sort field order was established has been deleted in the meantime. Refresh your page to start anew. Please note that your changes will be lost.')}
</span>
);
return;
}
throw error;
}
}
@ -459,7 +448,7 @@ export default class CUD extends Component {
{type !== 'option' &&
<Fieldset label={t('Field order')}>
<Dropdown id="orderListBefore" label={t('Listings (before)')} options={getOrderOptions('order_list')} help={t('Select the field before which this field should appeara in listings. To exclude the field from listings, select "Not visible".')}/>
<Dropdown id="orderListBefore" label={t('Listings (before)')} options={getOrderOptions('order_list')} help={t('Select the field before which this field should appear in listings. To exclude the field from listings, select "Not visible".')}/>
<Dropdown id="orderSubscribeBefore" label={t('Subscription form (before)')} options={getOrderOptions('order_subscribe')} help={t('Select the field before which this field should appear in new subscription form. To exclude the field from the new subscription form, select "Not visible".')}/>
<Dropdown id="orderManageBefore" label={t('Management form (before)')} options={getOrderOptions('order_manage')} help={t('Select the field before which this field should appear in subscription management. To exclude the field from the subscription management form, select "Not visible".')}/>
</Fieldset>

View file

@ -35,7 +35,7 @@ export default class List extends Component {
const columns = [
{ data: 4, title: "#" },
{ data: 1, title: t('Name'),
render: (data, cmd, rowData) => rowData[2] === 'option' ? <span><span className="glyphicon glyphicon-record" aria-hidden="true"></span> {data}</span> : data
render: (data, cmd, rowData) => rowData[2] === 'option' ? <span><Icon icon="record"/> {data}</span> : data
},
{ data: 2, title: t('Type'), render: data => this.fieldTypes[data].label, sortable: false, searchable: false },
{ data: 3, title: t('Merge Tag') },

View file

@ -262,7 +262,7 @@ export default class CUD extends Component {
name: '',
description: '',
selectedTemplate: 'layout',
namespace: null
namespace: mailtrainConfig.user.namespace
};
supplyDefaults(data);
@ -341,6 +341,7 @@ export default class CUD extends Component {
render() {
const t = this.props.t;
const isEdit = !!this.props.entity;
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
const templateOptGroups = [];
@ -369,7 +370,7 @@ export default class CUD extends Component {
return (
<div>
{isEdit &&
{canDelete &&
<DeleteModalDialog
stateOwner={this}
visible={this.props.action === 'delete'}
@ -433,7 +434,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save')}/>
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/lists/forms/${this.props.entity.id}/delete`}/>}
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/lists/forms/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>
</div>

View file

@ -21,8 +21,6 @@ import Share from '../shares/Share';
const getStructure = t => {
const subPaths = {};
return {
'': {
title: t('Home'),
@ -31,7 +29,7 @@ const getStructure = t => {
'lists': {
title: t('Lists'),
link: '/lists',
component: ListsList,
panelComponent: ListsList,
children: {
':listId([0-9]+)': {
title: resolved => t('List "{{name}}"', {name: resolved.list.name}),
@ -47,7 +45,7 @@ const getStructure = t => {
},
link: params => `/lists/${params.listId}/subscriptions`,
visible: resolved => resolved.list.permissions.includes('viewSubscriptions'),
render: props => <SubscriptionsList list={props.resolved.list} segments={props.resolved.segments} segmentId={qs.parse(props.location.search).segment} />,
panelRender: props => <SubscriptionsList list={props.resolved.list} segments={props.resolved.segments} segmentId={qs.parse(props.location.search).segment} />,
children: {
':subscriptionId([0-9]+)': {
title: resolved => resolved.subscription.email,
@ -60,7 +58,7 @@ const getStructure = t => {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/subscriptions/${params.subscriptionId}/edit`,
render: props => <SubscriptionsCUD action={props.match.params.action} entity={props.resolved.subscription} list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
panelRender: props => <SubscriptionsCUD action={props.match.params.action} entity={props.resolved.subscription} list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
}
}
},
@ -69,20 +67,20 @@ const getStructure = t => {
resolve: {
fieldsGrouped: params => `/rest/fields-grouped/${params.listId}`
},
render: props => <SubscriptionsCUD action="create" list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
panelRender: props => <SubscriptionsCUD action="create" list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
}
} },
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/edit`,
visible: resolved => resolved.list.permissions.includes('edit'),
render: props => <ListsCUD action={props.match.params.action} entity={props.resolved.list} />
panelRender: props => <ListsCUD action={props.match.params.action} entity={props.resolved.list} />
},
fields: {
title: t('Fields'),
link: params => `/lists/${params.listId}/fields/`,
visible: resolved => resolved.list.permissions.includes('manageFields'),
render: props => <FieldsList list={props.resolved.list} />,
panelRender: props => <FieldsList list={props.resolved.list} />,
children: {
':fieldId([0-9]+)': {
title: resolved => t('Field "{{name}}"', {name: resolved.field.name}),
@ -95,7 +93,7 @@ const getStructure = t => {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/fields/${params.fieldId}/edit`,
render: props => <FieldsCUD action={props.match.params.action} entity={props.resolved.field} list={props.resolved.list} fields={props.resolved.fields} />
panelRender: props => <FieldsCUD action={props.match.params.action} entity={props.resolved.field} list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
@ -104,7 +102,7 @@ const getStructure = t => {
resolve: {
fields: params => `/rest/fields/${params.listId}`
},
render: props => <FieldsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
panelRender: props => <FieldsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
@ -112,7 +110,7 @@ const getStructure = t => {
title: t('Segments'),
link: params => `/lists/${params.listId}/segments`,
visible: resolved => resolved.list.permissions.includes('manageSegments'),
render: props => <SegmentsList list={props.resolved.list} />,
panelRender: props => <SegmentsList list={props.resolved.list} />,
children: {
':segmentId([0-9]+)': {
title: resolved => t('Segment "{{name}}"', {name: resolved.segment.name}),
@ -125,7 +123,7 @@ const getStructure = t => {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/segments/${params.segmentId}/edit`,
render: props => <SegmentsCUD action={props.match.params.action} entity={props.resolved.segment} list={props.resolved.list} fields={props.resolved.fields} />
panelRender: props => <SegmentsCUD action={props.match.params.action} entity={props.resolved.segment} list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
@ -134,7 +132,7 @@ const getStructure = t => {
resolve: {
fields: params => `/rest/fields/${params.listId}`
},
render: props => <SegmentsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
panelRender: props => <SegmentsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
@ -142,18 +140,18 @@ const getStructure = t => {
title: t('Share'),
link: params => `/lists/${params.listId}/share`,
visible: resolved => resolved.list.permissions.includes('share'),
render: props => <Share title={t('Share')} entity={props.resolved.list} entityTypeId="list" />
panelRender: props => <Share title={t('Share')} entity={props.resolved.list} entityTypeId="list" />
}
}
},
create: {
title: t('Create'),
render: props => <ListsCUD action="create" />
panelRender: props => <ListsCUD action="create" />
},
forms: {
title: t('Custom Forms'),
link: '/lists/forms',
component: FormsList,
panelComponent: FormsList,
children: {
':formsId([0-9]+)': {
title: resolved => t('Custom Forms "{{name}}"', {name: resolved.forms.name}),
@ -166,19 +164,19 @@ const getStructure = t => {
title: t('Edit'),
link: params => `/lists/forms/${params.formsId}/edit`,
visible: resolved => resolved.forms.permissions.includes('edit'),
render: props => <FormsCUD action={props.match.params.action} entity={props.resolved.forms} />
panelRender: props => <FormsCUD action={props.match.params.action} entity={props.resolved.forms} />
},
share: {
title: t('Share'),
link: params => `/lists/forms/${params.formsId}/share`,
visible: resolved => resolved.forms.permissions.includes('share'),
render: props => <Share title={t('Share')} entity={props.resolved.forms} entityTypeId="customForm" />
panelRender: props => <Share title={t('Share')} entity={props.resolved.forms} entityTypeId="customForm" />
}
}
},
create: {
title: t('Create'),
render: props => <FormsCUD action="create" />
panelRender: props => <FormsCUD action="create" />
}
}
}
@ -194,6 +192,6 @@ export default function() {
<I18nextProvider i18n={ i18n }><Section root='/lists' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
};
}

View file

@ -178,16 +178,6 @@ export default class CUD extends Component {
this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.'));
}
} catch (error) {
if (error instanceof interoperableErrors.DependencyNotFoundError) {
this.setFormStatusMessage('danger',
<span>
<strong>{t('Your updates cannot be saved.')}</strong>{' '}
{t('It seems that another field upon which sort field order was established has been deleted in the meantime. Refresh your page to start anew. Please note that your changes will be lost.')}
</span>
);
return;
}
throw error;
}
}

View file

@ -64,19 +64,19 @@ export default class List extends Component {
@withAsyncErrorHandler
async deleteSubscription(id) {
await axios.delete(`/rest/subscriptions/${this.props.list.id}/${id}`);
this.subscriptionsTable.refresh();
this.blacklistTable.refresh();
}
@withAsyncErrorHandler
async unsubscribeSubscription(id) {
await axios.post(`/rest/subscriptions-unsubscribe/${this.props.list.id}/${id}`);
this.subscriptionsTable.refresh();
this.blacklistTable.refresh();
}
@withAsyncErrorHandler
async blacklistSubscription(id) {
await axios.post(`/rest/XXX/${this.props.list.id}/${id}`); // FIXME - add url one the blacklist functionality is in
this.subscriptionsTable.refresh();
async blacklistSubscription(email) {
await axios.post("/rest/blacklist", { email });
this.blacklistTable.refresh();
}
render() {
@ -86,11 +86,11 @@ export default class List extends Component {
const columns = [
{ data: 2, title: t('Email') },
{ data: 3, title: t('Status'), render: data => this.subscriptionStatusLabels[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() : '' }
];
let colIdx = 5;
let colIdx = 6;
for (const fld of list.listFields) {
@ -123,11 +123,12 @@ export default class List extends Component {
});
}
// FIXME - add condition here to show it only if not blacklisted already
actions.push({
label: <Icon icon="ban-circle" title={t('Blacklist')}/>,
action: () => this.blacklistSubscription(data[0])
});
if (!data[5]) {
actions.push({
label: <Icon icon="ban-circle" title={t('Blacklist')}/>,
action: () => this.blacklistSubscription(data[2])
});
}
actions.push({
label: <Icon icon="remove" title={t('Remove')}/>,
@ -169,7 +170,7 @@ export default class List extends Component {
</div>
<Table ref={node => this.subscriptionsTable = node} withHeader dataUrl={dataUrl} columns={columns} />
<Table ref={node => this.blacklistTable = node} withHeader dataUrl={dataUrl} columns={columns} />
</div>
);
}

View file

@ -5,6 +5,7 @@ import {SubscriptionStatus} from "../../../../shared/lists";
import {ACEEditor, CheckBoxGroup, DatePicker, Dropdown, InputField, RadioGroup, TextArea} from "../../lib/form";
import {formatBirthday, formatDate, parseBirthday, parseDate} from "../../../../shared/date";
import {getFieldKey} from '../../../../shared/lists';
import 'brace/mode/json';
export function getSubscriptionStatusLabels(t) {