Skip to content

Commit

Permalink
Added preview component with preview support for campaigns and templates
Browse files Browse the repository at this point in the history
  • Loading branch information
knadh committed Oct 26, 2018
1 parent 2121c25 commit a1b5a39
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 86 deletions.
23 changes: 13 additions & 10 deletions campaigns.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,26 @@ func handlePreviewCampaign(c echo.Context) error {
var (
app = c.Get("app").(*App)
id, _ = strconv.Atoi(c.Param("id"))
camps models.Campaigns
body = c.FormValue("body")

camp models.Campaign
)

if id < 1 {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID.")
}

err := app.Queries.GetCampaigns.Select(&camps, id, "", 0, 1)
err := app.Queries.GetCampaignForPreview.Get(&camp, id)
if err != nil {
if err == sql.ErrNoRows {
return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.")
}

return echo.NewHTTPError(http.StatusInternalServerError,
fmt.Sprintf("Error fetching campaign: %s", pqErrMsg(err)))
} else if len(camps) == 0 {
return echo.NewHTTPError(http.StatusBadRequest, "Campaign not found.")
}

var (
camp = camps[0]
sub models.Subscriber
)

var sub models.Subscriber
// Get a random subscriber from the campaign.
if err := app.Queries.GetOneCampaignSubscriber.Get(&sub, camp.ID); err != nil {
if err == sql.ErrNoRows {
Expand All @@ -126,7 +126,10 @@ func handlePreviewCampaign(c echo.Context) error {
}

// Compile the template.
tpl, err := runner.CompileMessageTemplate(`{{ template "content" . }}`, camp.Body)
if body == "" {
body = camp.Body
}
tpl, err := runner.CompileMessageTemplate(camp.TemplateBody, body)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error compiling template: %v", err))
}
Expand Down
21 changes: 19 additions & 2 deletions frontend/my/src/Campaign.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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"
Expand Down Expand Up @@ -352,6 +353,7 @@ class Campaign extends React.PureComponent {
record: {},
contentType: "richtext",
messengers: [],
previewRecord: null,
body: "",
currentTab: "form",
editor: null,
Expand Down Expand Up @@ -405,6 +407,10 @@ class Campaign extends React.PureComponent {
this.setState({ currentTab: tab })
}

handlePreview = (record) => {
this.setState({ previewRecord: record })
}

render() {
return (
<section className="content campaign">
Expand Down Expand Up @@ -457,7 +463,7 @@ class Campaign extends React.PureComponent {
formDisabled={ this.state.formDisabled }
/>
<div className="content-actions">
<p><Button icon="search">Preview</Button></p>
<p><Button icon="search" onClick={() => this.handlePreview(this.state.record)}>Preview</Button></p>
</div>
</Tabs.TabPane>
</Tabs>
Expand All @@ -471,8 +477,19 @@ class Campaign extends React.PureComponent {
insertMedia: this.state.editor ? this.state.editor.insertMedia : null,
onCancel: this.toggleMedia,
onOk: this.toggleMedia
} } />
}} />
</Modal>

{ this.state.previewRecord &&
<ModalPreview
title={ this.state.previewRecord.name }
body={ this.state.body }
previewURL={ cs.Routes.PreviewCampaign.replace(":id", this.state.previewRecord.id) }
onCancel={() => {
this.setState({ previewRecord: null })
}}
/>
}
</section>
)
}
Expand Down
35 changes: 22 additions & 13 deletions frontend/my/src/Campaigns.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Row, Col, Button, Table, Icon, Tooltip, Tag, Popconfirm, Progress, Moda
import dayjs from "dayjs"
import relativeTime from 'dayjs/plugin/relativeTime'

import ModalPreview from "./ModalPreview"
import * as cs from "./constants"

class Campaigns extends React.PureComponent {
Expand All @@ -15,6 +16,7 @@ class Campaigns extends React.PureComponent {
queryParams: "",
stats: {},
record: null,
previewRecord: null,
cloneName: "",
modalWaiting: false
}
Expand Down Expand Up @@ -110,10 +112,16 @@ class Campaigns extends React.PureComponent {
title: "",
dataIndex: "actions",
className: "actions",
width: "10%",
width: "20%",
render: (text, record) => {
return (
<div className="actions">
<Tooltip title="Preview campaign" placement="bottom">
<a role="button" onClick={() => {
this.handlePreview(record)
}}><Icon type="search" /></a>
</Tooltip>

<Tooltip title="Clone campaign" placement="bottom">
<a role="button" onClick={() => {
let r = { ...record, lists: record.lists.map((i) => { return i.id }) }
Expand Down Expand Up @@ -352,6 +360,10 @@ class Campaigns extends React.PureComponent {
})
}

handlePreview = (record) => {
this.setState({ previewRecord: record })
}

render() {
const pagination = {
...this.paginationOptions,
Expand All @@ -377,18 +389,15 @@ class Campaigns extends React.PureComponent {
pagination={ pagination }
/>

{ this.state.record &&
<Modal visible={ this.state.record } width="500px"
className="clone-campaign-modal"
title={ "Clone " + this.state.record.name}
okText="Clone"
confirmLoading={ this.state.modalWaiting }
onCancel={ this.handleToggleCloneForm }
onOk={() => { this.handleCloneCampaign({ ...this.state.record, name: this.state.cloneName }) }}>
<Input autoFocus defaultValue={ this.state.record.name } style={{ width: "100%" }} onChange={(e) => {
this.setState({ cloneName: e.target.value })
}} />
</Modal> }
{ this.state.previewRecord &&
<ModalPreview
title={ this.state.previewRecord.name }
previewURL={ cs.Routes.PreviewCampaign.replace(":id", this.state.previewRecord.id) }
onCancel={() => {
this.setState({ previewRecord: null })
}}
/>
}
</section>
)
}
Expand Down
54 changes: 54 additions & 0 deletions frontend/my/src/ModalPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react"
import { Modal } from "antd"
import * as cs from "./constants"

class ModalPreview extends React.PureComponent {
makeForm(body) {
let form = document.createElement("form")
form.method = cs.MethodPost
form.action = this.props.previewURL
form.target = "preview-iframe"

let input = document.createElement("input")
input.type = "hidden"
input.name = "body"
input.value = body
form.appendChild(input)
document.body.appendChild(form)
form.submit()
}

render () {
return (
<Modal visible={ true } title={ this.props.title }
className="preview-modal"
width="90%"
height={ 900 }
onCancel={ this.props.onCancel }
onOk={ this.props.onCancel }>
<div className="preview-iframe-container">
<iframe title={ this.props.title ? this.props.title : "Preview" }
name="preview-iframe"
id="preview-iframe"
className="preview-iframe"
ref={(o) => {
if(o) {
// When the DOM reference for the iframe is ready,
// see if there's a body to post with the form hack.
if(this.props.body !== undefined
&& this.props.body !== null) {
this.makeForm(this.props.body)
}
}
}}
src={ this.props.previewURL ? this.props.previewURL : "about:blank" }>
</iframe>

</div>
</Modal>

)
}
}

export default ModalPreview
108 changes: 65 additions & 43 deletions frontend/my/src/Templates.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from "react"
import { Row, Col, Modal, Form, Input, Button, Table, Icon, Tooltip, Tag, Popconfirm, Spin, notification } from "antd"

import ModalPreview from "./ModalPreview"
import Utils from "./utils"
import * as cs from "./constants"

class CreateFormDef extends React.PureComponent {
state = {
confirmDirty: false,
modalWaiting: false
modalWaiting: false,
previewName: "",
previewBody: ""
}

// Handle create / edit form submission.
Expand Down Expand Up @@ -50,6 +53,10 @@ class CreateFormDef extends React.PureComponent {
this.setState({ confirmDirty: this.state.confirmDirty || !!value })
}

handlePreview = (name, body) => {
this.setState({ previewName: name, previewBody: body })
}

render() {
const { formType, record, onClose } = this.props
const { getFieldDecorator } = this.props.form
Expand All @@ -64,37 +71,54 @@ class CreateFormDef extends React.PureComponent {
}

return (
<Modal visible={ true } title={ formType === cs.FormCreate ? "Add template" : record.name }
okText={ this.state.form === cs.FormCreate ? "Add" : "Save" }
width="90%"
height={ 900 }
confirmLoading={ this.state.modalWaiting }
onCancel={ onClose }
onOk={ this.handleSubmit }>

<Spin spinning={ this.props.reqStates[cs.ModelTemplates] === cs.StatePending }>
<Form onSubmit={this.handleSubmit}>
<Form.Item {...formItemLayout} label="Name">
{getFieldDecorator("name", {
initialValue: record.name,
rules: [{ required: true }]
})(<Input autoFocus maxLength="200" />)}
</Form.Item>
<Form.Item {...formItemLayout} name="body" label="Raw HTML">
{getFieldDecorator("body", { initialValue: record.body ? record.body : "", rules: [{ required: true }] })(
<Input.TextArea autosize={{ minRows: 10, maxRows: 30 }}>
</Input.TextArea>
)}
</Form.Item>
</Form>
</Spin>
<Row>
<Col span="4"></Col>
<Col span="18" className="text-grey text-small">
The placeholder <code>{'{'}{'{'} template "content" . {'}'}{'}'}</code> should appear in the template. <a href="" target="_blank">Read more on templating</a>.
</Col>
</Row>
</Modal>
<div>
<Modal visible={ true } title={ formType === cs.FormCreate ? "Add template" : record.name }
okText={ this.state.form === cs.FormCreate ? "Add" : "Save" }
width="90%"
height={ 900 }
confirmLoading={ this.state.modalWaiting }
onCancel={ onClose }
onOk={ this.handleSubmit }>

<Spin spinning={ this.props.reqStates[cs.ModelTemplates] === cs.StatePending }>
<Form onSubmit={this.handleSubmit}>
<Form.Item {...formItemLayout} label="Name">
{getFieldDecorator("name", {
initialValue: record.name,
rules: [{ required: true }]
})(<Input autoFocus maxLength="200" />)}
</Form.Item>
<Form.Item {...formItemLayout} name="body" label="Raw HTML">
{getFieldDecorator("body", { initialValue: record.body ? record.body : "", rules: [{ required: true }] })(
<Input.TextArea autosize={{ minRows: 10, maxRows: 30 }} />
)}
</Form.Item>
<Form.Item {...formItemLayout} colon={ false } label="&nbsp;">
<Button icon="search" onClick={ () =>
this.handlePreview(this.props.form.getFieldValue("name"), this.props.form.getFieldValue("body"))
}>Preview</Button>
</Form.Item>
</Form>
</Spin>
<Row>
<Col span="4"></Col>
<Col span="18" className="text-grey text-small">
The placeholder <code>{'{'}{'{'} template "content" . {'}'}{'}'}</code> should appear in the template. <a href="" target="_blank">Read more on templating</a>.
</Col>
</Row>
</Modal>

{ this.state.previewBody &&
<ModalPreview
title={ this.state.previewName ? this.state.previewName : "Template preview" }
previewURL={ cs.Routes.PreviewTemplate }
body={ this.state.previewBody }
onCancel={() => {
this.setState({ previewBody: null, previewName: null })
}}
/>
}
</div>
)
}
}
Expand Down Expand Up @@ -243,17 +267,15 @@ class Templates extends React.PureComponent {
fetchRecords = { this.fetchRecords }
/>

<Modal visible={ this.state.previewRecord !== null } title={ this.state.previewRecord ? this.state.previewRecord.name : "" }
className="template-preview-modal"
width="90%"
height={ 900 }
onOk={ () => { this.setState({ previewRecord: null }) } }>
{ this.state.previewRecord !== null &&
<iframe title="Template preview"
className="template-preview"
src={ cs.Routes.PreviewTemplate.replace(":id", this.state.previewRecord.id) }>
</iframe> }
</Modal>
{ this.state.previewRecord &&
<ModalPreview
title={ this.state.previewRecord.name }
previewURL={ cs.Routes.PreviewTemplate.replace(":id", this.state.previewRecord.id) }
onCancel={() => {
this.setState({ previewRecord: null })
}}
/>
}
</section>
)
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/my/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const Routes = {

GetSubscribers: "/api/subscribers",
GetSubscribersByList: "/api/subscribers/lists/:listID",
PreviewCampaign: "/api/campaigns/:id/preview",
CreateSubscriber: "/api/subscribers",
UpdateSubscriber: "/api/subscribers/:id",
DeleteSubscriber: "/api/subscribers/:id",
Expand All @@ -81,6 +82,7 @@ export const Routes = {

GetTemplates: "/api/templates",
PreviewTemplate: "/api/templates/:id/preview",
PreviewNewTemplate: "/api/templates/preview",
CreateTemplate: "/api/templates",
UpdateTemplate: "/api/templates/:id",
SetDefaultTemplate: "/api/templates/:id/default",
Expand Down
Loading

0 comments on commit a1b5a39

Please sign in to comment.