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 parseUrl from "querystring" 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 } } } 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 // eslint-disable-next-line 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) { return } this.setState({ quill: o }) document.querySelector(".ql-editor").focus() }} 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 = cb => { if (this.state.loading) { return } if (!cb) { // Set a fake callback. cb = () => {} } 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({ pathname: cs.Routes.ViewCampaign.replace( ":id", resp.data.data.id ), hash: "content-tab" }) cb(true) }) .catch(e => { notification["error"]({ placement: cs.MsgPosition, message: "Error", description: e.message }) this.setState({ loading: false }) cb(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 }) cb(true) }) .catch(e => { notification["error"]({ placement: cs.MsgPosition, message: "Error", description: e.message }) this.setState({ loading: false }) cb(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"]({ placement: cs.MsgPosition, 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) } else if (this.props.route.location.search) { // list_id in the query params. const p = parseUrl.parse(this.props.route.location.search.substring(1)) if (p.hasOwnProperty("list_id")) { // eslint-disable-next-line radix const id = parseInt(p.list_id) if (id) { subLists.push(id) } } } 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.length > 0 ? subLists : this.props.data[cs.ModelLists].hasOwnProperty( "results" ) && this.props.data[cs.ModelLists].results.length === 1 ? [this.props.data[cs.ModelLists].results[0].id] : undefined, rules: [{ required: true }] })( )} {getFieldDecorator("template_id", { initialValue: record.template_id ? record.template_id : this.props.data[cs.ModelTemplates].length > 0 ? this.props.data[cs.ModelTemplates].filter( t => t.is_default )[0].id : undefined, rules: [{ required: true }] })( )} {getFieldDecorator("tags", { initialValue: record.tags })( )}
)} ) } } 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: {}, formRef: null, 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, { per_page: "all" }) // 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 }) } // Content tab? if (document.location.hash === "#content-tab") { this.setCurrentTab("content") } } 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"]({ placement: cs.MsgPosition, 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}

ID {this.state.record.id} — UUID{" "} {this.state.record.uuid}
)} {!this.state.formDisabled && !this.state.loading && (
{" "} {this.state.record.status === cs.CampaignStatusDraft && this.state.record.send_at && ( { this.state.formRef.handleSubmit(() => { this.props.route.history.push({ pathname: cs.Routes.ViewCampaigns, state: { campaign: this.state.record, campaignStatus: cs.CampaignStatusScheduled } }) }) }} > )} {this.state.record.status === cs.CampaignStatusDraft && !this.state.record.send_at && ( { this.state.formRef.handleSubmit(() => { this.props.route.history.push({ pathname: cs.Routes.ViewCampaigns, state: { campaign: this.state.record, campaignStatus: cs.CampaignStatusRunning } }) }) }} > )}
)}

{ this.setState({ currentTab: t }) }} > { if (!r) { return } // Take the editor's reference and save it in the state // so that it's insertMedia() function can be passed to this.setState({ formRef: r }) }} record={this.state.record} isSingle={this.state.record.id ? true : false} body={ this.state.body ? this.state.body : this.state.record.body } contentType={this.state.contentType} formDisabled={this.state.formDisabled} fetchRecord={this.fetchRecord} setCurrentTab={this.setCurrentTab} /> {this.state.record.id && (
{ if (!r) { return } // Take the editor's reference and save it in the state // so that it's insertMedia() function can be passed to this.setState({ editor: r }) }} 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.record.id && }
{this.state.previewRecord && ( { this.setState({ previewRecord: null }) }} /> )}
) } } export default Campaign