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

{ 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 }] })( )} {getFieldDecorator("template_id", { initialValue: record.template_id, rules: [{ required: true }] })( )} {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 &&

{getFieldDecorator("subscribers")( )}
}
) } } 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.state.previewRecord && { this.setState({ previewRecord: null }) }} /> }
) } } export default Campaign