import React from "react"
import { Modal, Tabs, Row, Col, Form, Switch, Select, Radio, Tag, Input, Button, Icon, Spin, DatePicker, Popconfirm, notification } from "antd"
import * as cs from "./constants"
import Media from "./Media"
import ModalPreview from "./ModalPreview"
import moment from 'moment'
import ReactQuill from "react-quill"
import Delta from "quill-delta"
import "react-quill/dist/quill.snow.css"
const formItemLayout = {
labelCol: { xs: { span: 16 }, sm: { span: 4 } },
wrapperCol: { xs: { span: 16 }, sm: { span: 10 } }
}
const formItemTailLayout = {
wrapperCol: { xs: { span: 24, offset: 0 }, sm: { span: 10, offset: 4 } }
}
class Editor extends React.PureComponent {
state = {
editor: null,
quill: null,
rawInput: null,
selContentType: "richtext",
contentType: "richtext",
body: ""
}
quillModules = {
toolbar: {
container: [
[{"header": [1, 2, 3, false] }],
["bold", "italic", "underline", "strike", "blockquote", "code"],
[{ "color": [] }, { "background": [] }, { 'size': [] }],
[{"list": "ordered"}, {"list": "bullet"}, {"indent": "-1"}, {"indent": "+1"}],
[{"align": ""}, { "align": "center" }, { "align": "right" }, { "align": "justify" }],
["link", "image"],
["clean", "font"]
],
handlers: {
"image": () => {
this.props.toggleMedia()
}
}
}
}
componentDidMount = () => {
// The editor component will only load once the individual campaign metadata
// has loaded, i.e., record.body is guaranteed to be available here.
if(this.props.record && this.props.record.id) {
this.setState({
body: this.props.record.body,
contentType: this.props.record.content_type,
selContentType: this.props.record.content_type
})
}
}
// Custom handler for inserting images from the media popup.
insertMedia = (uri) => {
const quill = this.state.quill.getEditor()
let range = quill.getSelection(true);
quill.updateContents(new Delta()
.retain(range.index)
.delete(range.length)
.insert({ image: this.props.config.rootURL + uri })
, null);
}
handleSelContentType = (_, e) => {
this.setState({ selContentType: e.props.value })
}
handleSwitchContentType = () => {
this.setState({ contentType: this.state.selContentType })
if(!this.state.quill || !this.state.quill.editor || !this.state.rawInput) {
return
}
// Switching from richtext to html.
let body = ""
if(this.state.selContentType === "html") {
body = this.state.quill.editor.container.firstChild.innerHTML
this.state.rawInput.value = body
} else if(this.state.selContentType === "richtext") {
body = this.state.rawInput.value
this.state.quill.editor.clipboard.dangerouslyPasteHTML(body, "raw")
}
this.props.setContent(this.state.selContentType, body)
}
render() {
return (
{ !this.props.formDisabled &&
Content format
Rich Text
Raw HTML
{ this.state.contentType !== this.state.selContentType &&
}
}
{
if(o) {
this.setState({ quill: o })
}
}}
onChange={ () => {
if(!this.state.quill) {
return
}
this.props.setContent(this.state.contentType, this.state.quill.editor.root.innerHTML)
} }
/>
{
if(!o) {
return
}
this.setState({ rawInput: o.textAreaRef })
}}
onChange={ (e) => {
this.props.setContent(this.state.contentType, e.target.value)
}}
/>
)
}
}
class TheFormDef extends React.PureComponent {
state = {
editorVisible: false,
sendLater: false,
loading: false
}
componentWillReceiveProps(nextProps) {
const has = nextProps.isSingle && nextProps.record.send_at !== null
if(!has) {
return
}
if(this.state.sendLater !== has) {
this.setState({ sendLater: has })
}
}
validateEmail = (rule, value, callback) => {
if(!value.match(/(.+?)\s<(.+?)@(.+?)>/)) {
return callback("Format should be: Your Name ")
}
callback()
}
handleSendLater = (e) => {
this.setState({ sendLater: e })
}
// Handle create / edit form submission.
handleSubmit = (e) => {
e.preventDefault()
this.props.form.validateFields((err, values) => {
if (err) {
return
}
if(!values.tags) {
values.tags = []
}
values.body = this.props.body
values.content_type = this.props.contentType
// Create a new campaign.
this.setState({ loading: true })
if(!this.props.isSingle) {
this.props.modelRequest(cs.ModelCampaigns, cs.Routes.CreateCampaign, cs.MethodPost, values).then((resp) => {
notification["success"]({ placement: cs.MsgPosition,
message: "Campaign created",
description: `"${values["name"]}" created` })
this.props.route.history.push(cs.Routes.ViewCampaign.replace(":id", resp.data.data.id))
this.props.fetchRecord(resp.data.data.id)
this.props.setCurrentTab("content")
this.setState({ loading: false })
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
this.setState({ loading: false })
})
} else {
this.props.modelRequest(cs.ModelCampaigns, cs.Routes.UpdateCampaign, cs.MethodPut, { ...values, id: this.props.record.id }).then((resp) => {
notification["success"]({ placement: cs.MsgPosition,
message: "Campaign updated",
description: `"${values["name"]}" updated` })
this.setState({ loading: false })
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
this.setState({ loading: false })
})
}
})
}
handleTestCampaign = (e) => {
e.preventDefault()
this.props.form.validateFields((err, values) => {
if (err) {
return
}
if(!values.tags) {
values.tags = []
}
values.id = this.props.record.id
values.body = this.props.body
values.content_type = this.props.contentType
this.setState({ loading: true })
this.props.request(cs.Routes.TestCampaign, cs.MethodPost, values).then((resp) => {
this.setState({ loading: false })
notification["success"]({ placement: cs.MsgPosition,
message: "Test sent",
description: `Test messages sent` })
}).catch(e => {
this.setState({ loading: false })
notification["error"]({ message: "Error", description: e.message })
})
})
}
render() {
const { record } = this.props;
const { getFieldDecorator } = this.props.form
let subLists = []
if(this.props.isSingle && record.lists) {
subLists = record.lists.map((v) => { return v.id !== 0 ? v.id : null }).filter(v => v !== null)
}
if(this.record) {
this.props.pageTitle(record.name + " / Campaigns")
} else {
this.props.pageTitle("New campaign")
}
return (
{getFieldDecorator("name", {
extra: "This is internal and will not be visible to subscribers",
initialValue: record.name,
rules: [{ required: true }]
})( )}
{getFieldDecorator("subject", {
initialValue: record.subject,
rules: [{ required: true }]
})( )}
{getFieldDecorator("from_email", {
initialValue: record.from_email ? record.from_email : this.props.config.fromEmail,
rules: [{ required: true }, { validator: this.validateEmail }]
})( )}
{getFieldDecorator("lists", { initialValue: subLists, rules: [{ required: true }] })(
{[...this.props.data[cs.ModelLists]].map((v, i) =>
{ v["name"] }
)}
)}
{getFieldDecorator("template_id", { initialValue: record.template_id, rules: [{ required: true }] })(
{[...this.props.data[cs.ModelTemplates]].map((v, i) =>
{ v["name"] }
)}
)}
{getFieldDecorator("tags", { initialValue: record.tags })(
)}
{getFieldDecorator("messenger", { initialValue: record.messenger ? record.messenger : "email" })(
{[...this.props.config.messengers].map((v, i) =>
{ v }
)}
)}
{getFieldDecorator("send_later", { defaultChecked: this.props.isSingle })(
)}
{this.state.sendLater && getFieldDecorator("send_at",
{ initialValue: (record && typeof(record.send_at) === "string") ? moment(record.send_at) : moment(new Date()).add(1, "days").startOf("day") })(
)}
{ !this.props.formDisabled &&
{ !this.props.isSingle ? "Continue" : "Save changes" }
}
{ this.props.isSingle &&
{getFieldDecorator("subscribers")(
)}
Send test
}
)
}
}
const TheForm = Form.create()(TheFormDef)
class Campaign extends React.PureComponent {
state = {
campaignID: this.props.route.match.params ? parseInt(this.props.route.match.params.campaignID, 10) : 0,
record: {},
contentType: "richtext",
previewRecord: null,
body: "",
currentTab: "form",
editor: null,
loading: true,
mediaVisible: false,
formDisabled: false
}
componentDidMount = () => {
// Fetch lists.
this.props.modelRequest(cs.ModelLists, cs.Routes.GetLists, cs.MethodGet)
// Fetch templates.
this.props.modelRequest(cs.ModelTemplates, cs.Routes.GetTemplates, cs.MethodGet)
// Fetch campaign.
if(this.state.campaignID) {
this.fetchRecord(this.state.campaignID)
} else {
this.setState({ loading: false })
}
}
fetchRecord = (id) => {
this.props.request(cs.Routes.GetCampaign, cs.MethodGet, { id: id }).then((r) => {
const record = r.data.data
this.setState({ record: record, loading: false })
// The form for non draft and scheduled campaigns should be locked.
if(record.status !== cs.CampaignStatusDraft &&
record.status !== cs.CampaignStatusScheduled) {
this.setState({ formDisabled: true })
}
}).catch(e => {
notification["error"]({ message: "Error", description: e.message })
})
}
setContent = (contentType, body) => {
this.setState({ contentType: contentType, body: body })
}
toggleMedia = () => {
this.setState({ mediaVisible: !this.state.mediaVisible })
}
setCurrentTab = (tab) => {
this.setState({ currentTab: tab })
}
handlePreview = (record) => {
this.setState({ previewRecord: record })
}
render() {
return (
{ !this.state.record.id && Create a campaign }
{ this.state.record.id &&
{ this.state.record.status }
{ this.state.record.name }
}
{
this.setState({ currentTab: t })
}}>
{
// Take the editor's reference and save it in the state
// so that it's insertMedia() function can be passed to
this.setState({ editor: e })
}}
isSingle={ this.state.record.id ? true : false }
record={ this.state.record }
visible={ this.state.editorVisible }
toggleMedia={ this.toggleMedia }
setContent={ this.setContent }
formDisabled={ this.state.formDisabled }
/>
this.handlePreview(this.state.record)}>Preview
{ this.state.previewRecord &&
{
this.setState({ previewRecord: null })
}}
/>
}
)
}
}
export default Campaign