450 lines
14 KiB
Vue
450 lines
14 KiB
Vue
<template>
|
|
<section class="campaign">
|
|
<header class="columns">
|
|
<div class="column is-8">
|
|
<p v-if="isEditing" class="tags">
|
|
<b-tag v-if="isEditing" :class="data.status">{{ data.status }}</b-tag>
|
|
<b-tag v-if="data.type === 'optin'" :class="data.type">{{ data.type }}</b-tag>
|
|
<span v-if="isEditing" class="has-text-grey-light is-size-7">
|
|
{{ $t('globals.fields.id') }}: {{ data.id }} /
|
|
{{ $t('globals.fields.uuid') }}: {{ data.uuid }}
|
|
</span>
|
|
</p>
|
|
<h4 v-if="isEditing" class="title is-4">{{ data.name }}</h4>
|
|
<h4 v-else class="title is-4">{{ $t('campaigns.newCampaign') }}</h4>
|
|
</div>
|
|
|
|
<div class="column">
|
|
<div class="buttons" v-if="isEditing && canEdit">
|
|
<b-button @click="onSubmit" :loading="loading.campaigns"
|
|
type="is-primary" icon-left="content-save-outline" data-cy="btn-save">
|
|
{{ $t('globals.buttons.saveChanges') }}
|
|
</b-button>
|
|
<b-button v-if="canStart" @click="startCampaign" :loading="loading.campaigns"
|
|
type="is-primary" icon-left="rocket-launch-outline" data-cy="btn-start">
|
|
{{ $t('campaigns.start') }}
|
|
</b-button>
|
|
<b-button v-if="canSchedule" @click="startCampaign" :loading="loading.campaigns"
|
|
type="is-primary" icon-left="clock-start" data-cy="btn-schedule">
|
|
{{ $t('campaigns.schedule') }}
|
|
</b-button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<b-loading :active="loading.campaigns"></b-loading>
|
|
|
|
<b-tabs type="is-boxed" :animated="false" v-model="activeTab">
|
|
<b-tab-item :label="$tc('globals.terms.campaign')" label-position="on-border"
|
|
icon="rocket-launch-outline">
|
|
<section class="wrap">
|
|
<div class="columns">
|
|
<div class="column is-7">
|
|
<form @submit.prevent="onSubmit">
|
|
<b-field :label="$t('globals.fields.name')" label-position="on-border">
|
|
<b-input :maxlength="200" :ref="'focus'" v-model="form.name"
|
|
name="name" :disabled="!canEdit"
|
|
:placeholder="$t('globals.fields.name')" required></b-input>
|
|
</b-field>
|
|
|
|
<b-field :label="$t('campaigns.subject')" label-position="on-border">
|
|
<b-input :maxlength="200" v-model="form.subject"
|
|
name="subject" :disabled="!canEdit"
|
|
:placeholder="$t('campaigns.subject')" required></b-input>
|
|
</b-field>
|
|
|
|
<b-field :label="$t('campaigns.fromAddress')" label-position="on-border">
|
|
<b-input :maxlength="200" v-model="form.fromEmail"
|
|
name="from_email" :disabled="!canEdit"
|
|
:placeholder="$t('campaigns.fromAddressPlaceholder')" required></b-input>
|
|
</b-field>
|
|
|
|
<list-selector
|
|
v-model="form.lists"
|
|
:selected="form.lists"
|
|
:all="lists.results"
|
|
:disabled="!canEdit"
|
|
:label="$t('globals.terms.lists')"
|
|
:placeholder="$t('campaigns.sendToLists')"
|
|
></list-selector>
|
|
|
|
<b-field :label="$tc('globals.terms.template')" label-position="on-border">
|
|
<b-select :placeholder="$tc('globals.terms.template')" v-model="form.templateId"
|
|
name="template" :disabled="!canEdit" required>
|
|
<option v-for="t in templates" :value="t.id" :key="t.id">{{ t.name }}</option>
|
|
</b-select>
|
|
</b-field>
|
|
|
|
<b-field :label="$tc('globals.terms.messenger')" label-position="on-border">
|
|
<b-select :placeholder="$tc('globals.terms.messenger')" v-model="form.messenger"
|
|
name="messenger" :disabled="!canEdit" required>
|
|
<option v-for="m in messengers"
|
|
:value="m" :key="m">{{ m }}</option>
|
|
</b-select>
|
|
</b-field>
|
|
|
|
<b-field :label="$t('globals.terms.tags')" label-position="on-border">
|
|
<b-taginput v-model="form.tags" name="tags" :disabled="!canEdit"
|
|
ellipsis icon="tag-outline" :placeholder="$t('globals.terms.tags')" />
|
|
</b-field>
|
|
<hr />
|
|
|
|
<div class="columns">
|
|
<div class="column is-4">
|
|
<b-field :label="$t('campaigns.sendLater')" data-cy="btn-send-later">
|
|
<b-switch v-model="form.sendLater" :disabled="!canEdit" />
|
|
</b-field>
|
|
</div>
|
|
<div class="column">
|
|
<br />
|
|
<b-field v-if="form.sendLater" data-cy="send_at"
|
|
:message="form.sendAtDate ? $utils.duration(Date(), form.sendAtDate) : ''">
|
|
<b-datetimepicker
|
|
v-model="form.sendAtDate"
|
|
:disabled="!canEdit"
|
|
:placeholder="$t('campaigns.dateAndTime')"
|
|
icon="calendar-clock"
|
|
:timepicker="{ hourFormat: '24' }"
|
|
:datetime-formatter="formatDateTime"
|
|
horizontal-time-picker>
|
|
</b-datetimepicker>
|
|
</b-field>
|
|
</div>
|
|
</div>
|
|
<hr />
|
|
|
|
<b-field v-if="isNew">
|
|
<b-button native-type="submit" type="is-primary"
|
|
:loading="loading.campaigns" data-cy="btn-continue">
|
|
{{ $t('campaigns.continue') }}
|
|
</b-button>
|
|
</b-field>
|
|
</form>
|
|
</div>
|
|
<div class="column is-4 is-offset-1">
|
|
<br />
|
|
<div class="box">
|
|
<h3 class="title is-size-6">{{ $t('campaigns.sendTest') }}</h3>
|
|
<b-field :message="$t('campaigns.sendTestHelp')">
|
|
<b-taginput v-model="form.testEmails"
|
|
:before-adding="$utils.validateEmail" :disabled="this.isNew"
|
|
ellipsis icon="email-outline" :placeholder="$t('campaigns.testEmails')" />
|
|
</b-field>
|
|
<b-field>
|
|
<b-button @click="sendTest" :loading="loading.campaigns" :disabled="this.isNew"
|
|
type="is-primary" icon-left="email-outline">
|
|
{{ $t('campaigns.send') }}
|
|
</b-button>
|
|
</b-field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</b-tab-item><!-- campaign -->
|
|
|
|
<b-tab-item :label="$t('campaigns.content')" icon="text" :disabled="isNew">
|
|
<editor
|
|
v-model="form.content"
|
|
:id="data.id"
|
|
:title="data.name"
|
|
:contentType="data.contentType"
|
|
:body="data.body"
|
|
:disabled="!canEdit"
|
|
/>
|
|
|
|
<div v-if="canEdit && form.content.contentType !== 'plain'" class="alt-body">
|
|
<p class="is-size-6 has-text-grey has-text-right">
|
|
<a v-if="form.altbody === null" href="#" @click.prevent="addAltBody">
|
|
<b-icon icon="text" size="is-small" /> {{ $t('campaigns.addAltText') }}
|
|
</a>
|
|
<a v-else href="#" @click.prevent="$utils.confirm(null, removeAltBody)">
|
|
<b-icon icon="trash-can-outline" size="is-small" />
|
|
{{ $t('campaigns.removeAltText') }}
|
|
</a>
|
|
</p>
|
|
<br />
|
|
<b-input v-if="form.altbody !== null" v-model="form.altbody"
|
|
type="textarea" :disabled="!canEdit" />
|
|
</div>
|
|
</b-tab-item><!-- content -->
|
|
</b-tabs>
|
|
</section>
|
|
</template>
|
|
|
|
<script>
|
|
import Vue from 'vue';
|
|
import { mapState } from 'vuex';
|
|
import dayjs from 'dayjs';
|
|
import htmlToPlainText from 'textversionjs';
|
|
|
|
import ListSelector from '../components/ListSelector.vue';
|
|
import Editor from '../components/Editor.vue';
|
|
|
|
export default Vue.extend({
|
|
components: {
|
|
ListSelector,
|
|
Editor,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
isNew: false,
|
|
isEditing: false,
|
|
activeTab: 0,
|
|
|
|
data: {},
|
|
|
|
// IDs from ?list_id query param.
|
|
selListIDs: [],
|
|
|
|
// Binds form input values.
|
|
form: {
|
|
name: '',
|
|
subject: '',
|
|
fromEmail: '',
|
|
templateId: 0,
|
|
lists: [],
|
|
tags: [],
|
|
sendAt: null,
|
|
content: { contentType: 'richtext', body: '' },
|
|
altbody: null,
|
|
|
|
// Parsed Date() version of send_at from the API.
|
|
sendAtDate: null,
|
|
sendLater: false,
|
|
|
|
testEmails: [],
|
|
},
|
|
};
|
|
},
|
|
|
|
methods: {
|
|
formatDateTime(s) {
|
|
return dayjs(s).format('YYYY-MM-DD HH:mm');
|
|
},
|
|
|
|
addAltBody() {
|
|
this.form.altbody = htmlToPlainText(this.form.content.body);
|
|
},
|
|
|
|
removeAltBody() {
|
|
this.form.altbody = null;
|
|
},
|
|
|
|
onSubmit() {
|
|
if (this.isNew) {
|
|
this.createCampaign();
|
|
} else {
|
|
this.updateCampaign();
|
|
}
|
|
},
|
|
|
|
getCampaign(id) {
|
|
return this.$api.getCampaign(id).then((data) => {
|
|
this.data = data;
|
|
this.form = {
|
|
...this.form,
|
|
...data,
|
|
|
|
// The structure that is populated by editor input event.
|
|
content: { contentType: data.contentType, body: data.body },
|
|
};
|
|
|
|
if (data.sendAt !== null) {
|
|
this.form.sendLater = true;
|
|
this.form.sendAtDate = dayjs(data.sendAt).toDate();
|
|
}
|
|
});
|
|
},
|
|
|
|
sendTest() {
|
|
const data = {
|
|
id: this.data.id,
|
|
name: this.form.name,
|
|
subject: this.form.subject,
|
|
lists: this.form.lists.map((l) => l.id),
|
|
from_email: this.form.fromEmail,
|
|
messenger: this.form.messenger,
|
|
type: 'regular',
|
|
tags: this.form.tags,
|
|
template_id: this.form.templateId,
|
|
content_type: this.form.content.contentType,
|
|
body: this.form.content.body,
|
|
altbody: this.form.content.contentType !== 'plain' ? this.form.altbody : null,
|
|
subscribers: this.form.testEmails,
|
|
};
|
|
|
|
this.$api.testCampaign(data).then(() => {
|
|
this.$utils.toast(this.$t('campaigns.testSent'));
|
|
});
|
|
return false;
|
|
},
|
|
|
|
createCampaign() {
|
|
const data = {
|
|
name: this.form.name,
|
|
subject: this.form.subject,
|
|
lists: this.form.lists.map((l) => l.id),
|
|
from_email: this.form.fromEmail,
|
|
content_type: 'richtext',
|
|
messenger: 'email',
|
|
type: 'regular',
|
|
tags: this.form.tags,
|
|
template_id: this.form.templateId,
|
|
// body: this.form.body,
|
|
};
|
|
|
|
this.$api.createCampaign(data).then((d) => {
|
|
this.$router.push({ name: 'campaign', hash: '#content', params: { id: d.id } });
|
|
});
|
|
return false;
|
|
},
|
|
|
|
async updateCampaign(typ) {
|
|
const data = {
|
|
name: this.form.name,
|
|
subject: this.form.subject,
|
|
lists: this.form.lists.map((l) => l.id),
|
|
from_email: this.form.fromEmail,
|
|
messenger: this.form.messenger,
|
|
type: 'regular',
|
|
tags: this.form.tags,
|
|
send_later: this.form.sendLater,
|
|
send_at: this.form.sendLater ? this.form.sendAtDate : null,
|
|
template_id: this.form.templateId,
|
|
content_type: this.form.content.contentType,
|
|
body: this.form.content.body,
|
|
altbody: this.form.content.contentType !== 'plain' ? this.form.altbody : null,
|
|
};
|
|
|
|
let typMsg = 'globals.messages.updated';
|
|
if (typ === 'start') {
|
|
typMsg = 'campaigns.started';
|
|
}
|
|
|
|
// This promise is used by startCampaign to first save before starting.
|
|
return new Promise((resolve) => {
|
|
this.$api.updateCampaign(this.data.id, data).then((d) => {
|
|
this.data = d;
|
|
this.$utils.toast(this.$t(typMsg, { name: d.name }));
|
|
resolve();
|
|
});
|
|
});
|
|
},
|
|
|
|
// Starts or schedule a campaign.
|
|
startCampaign() {
|
|
let status = '';
|
|
if (this.canStart) {
|
|
status = 'running';
|
|
} else if (this.canSchedule) {
|
|
status = 'scheduled';
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
this.$utils.confirm(null,
|
|
() => {
|
|
// First save the campaign.
|
|
this.updateCampaign().then(() => {
|
|
// Then start/schedule it.
|
|
this.$api.changeCampaignStatus(this.data.id, status).then(() => {
|
|
this.$router.push({ name: 'campaigns' });
|
|
});
|
|
});
|
|
});
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
...mapState(['settings', 'loading', 'lists', 'templates']),
|
|
|
|
canEdit() {
|
|
return this.isNew
|
|
|| this.data.status === 'draft' || this.data.status === 'scheduled';
|
|
},
|
|
|
|
canSchedule() {
|
|
return this.data.status === 'draft' && this.data.sendAt;
|
|
},
|
|
|
|
canStart() {
|
|
return this.data.status === 'draft' && !this.data.sendAt;
|
|
},
|
|
|
|
selectedLists() {
|
|
if (this.selListIDs.length === 0 || !this.lists.results) {
|
|
return [];
|
|
}
|
|
|
|
return this.lists.results.filter((l) => this.selListIDs.indexOf(l.id) > -1);
|
|
},
|
|
|
|
messengers() {
|
|
return ['email', ...this.settings.messengers.map((m) => m.name)];
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
selectedLists() {
|
|
this.form.lists = this.selectedLists;
|
|
},
|
|
},
|
|
|
|
mounted() {
|
|
this.form.fromEmail = this.settings['app.from_email'];
|
|
|
|
const { id } = this.$route.params;
|
|
|
|
// New campaign.
|
|
if (id === 'new') {
|
|
this.isNew = true;
|
|
|
|
if (this.$route.query.list_id) {
|
|
// Multiple list_id query params.
|
|
let strIds = [];
|
|
if (typeof this.$route.query.list_id === 'object') {
|
|
strIds = this.$route.query.list_id;
|
|
} else {
|
|
strIds = [this.$route.query.list_id];
|
|
}
|
|
|
|
this.selListIDs = strIds.map((v) => parseInt(v, 10));
|
|
}
|
|
} else {
|
|
const intID = parseInt(id, 10);
|
|
if (intID <= 0 || Number.isNaN(intID)) {
|
|
this.$utils.toast(this.$t('campaigns.invalid'));
|
|
return;
|
|
}
|
|
|
|
this.isEditing = true;
|
|
}
|
|
|
|
// Get templates list.
|
|
this.$api.getTemplates().then((data) => {
|
|
if (data.length > 0) {
|
|
if (!this.form.templateId) {
|
|
this.form.templateId = data.find((i) => i.isDefault === true).id;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Fetch campaign.
|
|
if (this.isEditing) {
|
|
this.getCampaign(id).then(() => {
|
|
if (this.$route.hash === '#content') {
|
|
this.activeTab = 1;
|
|
}
|
|
});
|
|
} else {
|
|
this.form.messenger = 'email';
|
|
}
|
|
|
|
this.$nextTick(() => {
|
|
this.$refs.focus.focus();
|
|
});
|
|
},
|
|
});
|
|
</script>
|