import React from "react" import { Link } from "react-router-dom" import { Row, Col, Modal, Form, Input, Select, Button, Table, Icon, Tooltip, Tag, Popconfirm, Spin, notification, Radio } from "antd" import Utils from "./utils" import * as cs from "./constants" const tagColors = { "enabled": "green", "blacklisted": "red" } class CreateFormDef extends React.PureComponent { state = { confirmDirty: false, attribs: {}, modalWaiting: false } componentDidMount() { this.setState({ attribs: this.props.record.attribs }) } // Handle create / edit form submission. handleSubmit = (e) => { e.preventDefault() var err = null, values = {} this.props.form.validateFields((e, v) => { err = e values = v }) if(err) { return } values["attribs"] = {} let a = this.props.form.getFieldValue("attribs-json") if(a && a.length > 0) { try { values["attribs"] = JSON.parse(a) if(values["attribs"] instanceof Array) { notification["error"]({ message: "Invalid JSON type", description: "Attributes should be a map {} and not an array []" }) return } } catch(e) { notification["error"]({ message: "Invalid JSON in attributes", description: e.toString() }) return } } this.setState({ modalWaiting: true }) if (this.props.formType === cs.FormCreate) { // Add a subscriber. this.props.modelRequest(cs.ModelSubscribers, cs.Routes.CreateSubscriber, cs.MethodPost, values).then(() => { notification["success"]({ message: "Subscriber added", description: `${values["email"]} added` }) this.props.fetchRecords() this.props.onClose() this.setState({ modalWaiting: false }) }).catch(e => { notification["error"]({ message: "Error", description: e.message }) this.setState({ modalWaiting: false }) }) } else { // Edit a subscriber. delete(values["keys"]) delete(values["vals"]) this.props.modelRequest(cs.ModelSubscribers, cs.Routes.UpdateSubscriber, cs.MethodPut, { ...values, id: this.props.record.id }).then(() => { notification["success"]({ message: "Subscriber modified", description: `${values["email"]} modified` }) // Reload the table. this.props.fetchRecords() this.props.onClose() this.setState({ modalWaiting: false }) }).catch(e => { notification["error"]({ message: "Error", description: e.message }) this.setState({ modalWaiting: false }) }) } } modalTitle(formType, record) { if(formType === cs.FormCreate) { return "Add subscriber" } return ( { record.status } {" "} { record.name } ({ record.email }) ) } render() { const { formType, record, onClose } = this.props; const { getFieldDecorator } = this.props.form const formItemLayout = { labelCol: { xs: { span: 16 }, sm: { span: 4 } }, wrapperCol: { xs: { span: 16 }, sm: { span: 18 } } } if (formType === null) { return null } let subListIDs = [] let subStatuses = {} if(this.props.record && this.props.record.lists) { subListIDs = this.props.record.lists.map((v) => { return v["id"] }) subStatuses = this.props.record.lists.reduce((o, item) => ({ ...o, [item.id]: item.subscription_status}), {}) } else if(this.props.list) { subListIDs = [ this.props.list.id ] } return (
{getFieldDecorator("email", { initialValue: record.email, rules: [{ required: true }] })()} {getFieldDecorator("name", { initialValue: record.name, rules: [{ required: true }] })()} {getFieldDecorator("status", { initialValue: record.status ? record.status : "enabled", rules: [{ required: true, message: "Type is required" }] })( )} {getFieldDecorator("lists", { initialValue: subListIDs })( )}

Attributes

Attributes can be defined as a JSON map, for example: {'{"age": 30, "color": "red", "is_user": true}'}. More info.

{getFieldDecorator("attribs-json", { initialValue: JSON.stringify(this.state.attribs, null, 4) })( )}
) } } class ListsFormDef extends React.PureComponent { state = { modalWaiting: false } // Handle create / edit form submission. handleSubmit = (e) => { e.preventDefault() var err = null, values = {} this.props.form.validateFields((e, v) => { err = e values = v }) if(err) { return } if(this.props.allRowsSelected) { values["list_ids"] = this.props.listIDs values["query"] = this.props.query } else { values["ids"] = this.props.selectedRows.map(r => r.id) } this.setState({ modalWaiting: true }) this.props.request(!this.props.allRowsSelected ? cs.Routes.AddSubscribersToLists : cs.Routes.AddSubscribersToListsByQuery, cs.MethodPut, values).then(() => { notification["success"]({ message: "Lists changed", description: `Lists changed for selected subscribers` }) this.props.clearSelectedRows() this.props.fetchRecords() this.setState({ modalWaiting: false }) this.props.onClose() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) this.setState({ modalWaiting: false }) }) } render() { const { getFieldDecorator } = this.props.form const formItemLayout = { labelCol: { xs: { span: 16 }, sm: { span: 4 } }, wrapperCol: { xs: { span: 16 }, sm: { span: 18 } } } return (
{getFieldDecorator("action", { initialValue: "add", rules: [{ required: true }] })( Add Remove Mark as unsubscribed )} {getFieldDecorator("target_list_ids", { rules:[{ required: true }] })( )}
) } } const CreateForm = Form.create()(CreateFormDef) const ListsForm = Form.create()(ListsFormDef) class Subscribers extends React.PureComponent { defaultPerPage = 20 state = { formType: null, listsFormVisible: false, record: {}, queryParams: { page: 1, total: 0, perPage: this.defaultPerPage, listID: this.props.route.match.params.listID ? parseInt(this.props.route.match.params.listID, 10) : 0, list: null, query: null, targetLists: [] }, listModalVisible: false, allRowsSelected: false, selectedRows: [] } // Pagination config. paginationOptions = { hideOnSinglePage: true, showSizeChanger: true, showQuickJumper: true, defaultPageSize: this.defaultPerPage, pageSizeOptions: ["20", "50", "70", "100"], position: "both", showTotal: (total, range) => `${range[0]} to ${range[1]} of ${total}`, onChange: (page, perPage) => { this.fetchRecords({ page: page, per_page: perPage }) }, onShowSizeChange: (page, perPage) => { this.fetchRecords({ page: page, per_page: perPage }) } } constructor(props) { super(props) // Table layout. this.columns = [{ title: "E-mail", dataIndex: "email", sorter: true, width: "25%", render: (text, record) => { const out = []; out.push(
{ this.handleShowEditForm(record)}}>{text}
) if(record.lists.length > 0) { for (let i = 0; i < record.lists.length; i++) { out.push( { record.lists[i].name } ) } } return out } }, { title: "Name", dataIndex: "name", sorter: true, width: "15%", render: (text, record) => { return ( this.handleShowEditForm(record)}>{text} ) } }, { title: "Status", dataIndex: "status", width: "5%", render: (status, _) => { return { status } } }, { title: "Lists", dataIndex: "lists", width: "10%", align: "center", render: (lists, _) => { return { lists.reduce((def, item) => def + (item.subscription_status !== cs.SubscriptionStatusUnsubscribed ? 1 : 0), 0) } } }, { title: "Created", width: "10%", dataIndex: "created_at", render: (date, _) => { return Utils.DateString(date) } }, { title: "Updated", width: "10%", dataIndex: "updated_at", render: (date, _) => { return Utils.DateString(date) } }, { title: "", dataIndex: "actions", width: "10%", render: (text, record) => { return (
{/* */} this.handleShowEditForm(record)}> this.handleDeleteRecord(record)}>
) } } ] } componentDidMount() { // Load lists on boot. this.props.modelRequest(cs.ModelLists, cs.Routes.GetLists, cs.MethodGet).then(() => { // If this is an individual list's view, pick up that list. if(this.state.queryParams.listID) { this.props.data[cs.ModelLists].forEach((l) => { if(l.id === this.state.queryParams.listID) { this.setState({ queryParams: { ...this.state.queryParams, list: l }}) return false } }) } }) this.fetchRecords() } fetchRecords = (params) => { let qParams = { page: this.state.queryParams.page, per_page: this.state.queryParams.per_page, list_id: this.state.queryParams.listID, query: this.state.queryParams.query } // The records are for a specific list. if(this.state.queryParams.listID) { qParams.list_id = this.state.queryParams.listID } if(params) { qParams = { ...qParams, ...params } } this.props.modelRequest(cs.ModelSubscribers, cs.Routes.GetSubscribers, cs.MethodGet, qParams).then(() => { this.setState({ queryParams: { ...this.state.queryParams, total: this.props.data[cs.ModelSubscribers].total, perPage: this.props.data[cs.ModelSubscribers].per_page, page: this.props.data[cs.ModelSubscribers].page, query: this.props.data[cs.ModelSubscribers].query, }}) }) } handleDeleteRecord = (record) => { this.props.modelRequest(cs.ModelSubscribers, cs.Routes.DeleteSubscriber, cs.MethodDelete, { id: record.id }) .then(() => { notification["success"]({ message: "Subscriber deleted", description: `${record.email} deleted` }) // Reload the table. this.fetchRecords() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) }) } handleDeleteRecords = (records) => { this.props.modelRequest(cs.ModelSubscribers, cs.Routes.DeleteSubscribers, cs.MethodDelete, { id: records.map(r => r.id) }) .then(() => { notification["success"]({ message: "Subscriber(s) deleted", description: "Selected subscribers deleted" }) // Reload the table. this.fetchRecords() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) }) } handleBlacklistSubscribers = (records) => { this.props.request(cs.Routes.BlacklistSubscribers, cs.MethodPut, { ids: records.map(r => r.id) }) .then(() => { notification["success"]({ message: "Subscriber(s) blacklisted", description: "Selected subscribers blacklisted" }) // Reload the table. this.fetchRecords() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) }) } // Arbitrary query based calls. handleDeleteRecordsByQuery = (listIDs, query) => { this.props.modelRequest(cs.ModelSubscribers, cs.Routes.DeleteSubscribersByQuery, cs.MethodPost, { list_ids: listIDs, query: query }) .then(() => { notification["success"]({ message: "Subscriber(s) deleted", description: "Selected subscribers have been deleted" }) // Reload the table. this.fetchRecords() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) }) } handleBlacklistSubscribersByQuery = (listIDs, query) => { this.props.request(cs.Routes.BlacklistSubscribersByQuery, cs.MethodPut, { list_ids: listIDs, query: query }) .then(() => { notification["success"]({ message: "Subscriber(s) blacklisted", description: "Selected subscribers have been blacklisted" }) // Reload the table. this.fetchRecords() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) }) } handleQuerySubscribersIntoLists = (query, sourceList, targetLists) => { let params = { query: query, source_list: sourceList, target_lists: targetLists } this.props.request(cs.Routes.QuerySubscribersIntoLists, cs.MethodPost, params).then((res) => { notification["success"]({ message: "Subscriber(s) added", description: `${ res.data.data.count } added` }) this.handleToggleListModal() }).catch(e => { notification["error"]({ message: "Error", description: e.message }) }) } handleHideForm = () => { this.setState({ formType: null }) } handleShowCreateForm = () => { this.setState({ formType: cs.FormCreate, attribs: [], record: {} }) } handleShowEditForm = (record) => { this.setState({ formType: cs.FormEdit, record: record }) } handleToggleListsForm = () => { this.setState({ listsFormVisible: !this.state.listsFormVisible }) } handleSearch = (q) => { q = q.trim().toLowerCase() if(q === "") { this.fetchRecords({ query: null }) return } q = q.replace(/'/g, "''") const query = `(name LIKE '%${q}%' OR email LIKE '%${q}%')` this.fetchRecords({ query: query }) } handleSelectRow = (_, records) => { this.setState({ allRowsSelected: false, selectedRows: records }) } handleSelectAllRows = () => { this.setState({ allRowsSelected: true, selectedRows: this.props.data[cs.ModelSubscribers].results }) } clearSelectedRows = (_, records) => { this.setState({ allRowsSelected: false, selectedRows: [] }) } handleToggleQueryForm = () => { this.setState({ queryFormVisible: !this.state.queryFormVisible }) } handleToggleListModal = () => { this.setState({ listModalVisible: !this.state.listModalVisible }) } render() { const pagination = { ...this.paginationOptions, ...this.state.queryParams } if(this.state.queryParams.list) { this.props.pageTitle(this.state.queryParams.list.name + " / Subscribers") } else { this.props.pageTitle("Subscribers") } return (

Subscribers { this.props.data[cs.ModelSubscribers].total > 0 && ({ this.props.data[cs.ModelSubscribers].total }) } { this.state.queryParams.list && » { this.state.queryParams.list.name } }

{" "}
Advanced
{ this.state.queryFormVisible &&

{ this.setState({ queryParams: { ...this.state.queryParams, query: e.target.value } }) }} value={ this.state.queryParams.query } autosize={{ minRows: 2, maxRows: 10 }} /> Write a partial SQL expression to query the subscribers based on their primary information or attributes. Learn more.

{" "}

} { this.state.selectedRows.length > 0 && }
`sub-${record.id}` } dataSource={ this.props.data[cs.ModelSubscribers].results } loading={ this.props.reqStates[cs.ModelSubscribers] !== cs.StateDone } pagination={ pagination } rowSelection = {{ columnWidth: "5%", onChange: this.handleSelectRow, selectedRowKeys: this.state.selectedRows.map(r => `sub-${r.id}`) }} /> { this.state.formType !== null && } { this.state.listsFormVisible && } ) } } export default Subscribers