2019-03-09 08:46:47 +01:00
|
|
|
import React from "react"
|
|
|
|
import Utils from "./utils"
|
|
|
|
import { BrowserRouter } from "react-router-dom"
|
2018-11-06 04:31:24 +01:00
|
|
|
import { Icon, notification } from "antd"
|
2019-03-09 08:46:47 +01:00
|
|
|
import axios from "axios"
|
|
|
|
import qs from "qs"
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2018-11-28 14:11:00 +01:00
|
|
|
import logo from "./static/listmonk.svg"
|
2019-03-09 08:46:47 +01:00
|
|
|
import Layout from "./Layout"
|
|
|
|
import * as cs from "./constants"
|
2018-10-25 15:51:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
App acts as a an "automagic" wrapper for all sub components. It is also the central
|
|
|
|
store for data required by various child components. In addition, all HTTP requests
|
|
|
|
are fired through App.requests(), where successful responses are set in App's state
|
|
|
|
for child components to access via this.props.data[type]. The structure is as follows:
|
|
|
|
App.state.data = {
|
|
|
|
"lists": [],
|
|
|
|
"subscribers": []
|
|
|
|
// etc.
|
|
|
|
}
|
|
|
|
|
|
|
|
A number of assumptions are made here for the "automagic" behaviour.
|
|
|
|
1. All responses to resources return lists
|
|
|
|
2. All PUT, POST, DELETE requests automatically append /:id to the API URIs.
|
|
|
|
*/
|
|
|
|
|
|
|
|
class App extends React.PureComponent {
|
2019-03-09 08:46:47 +01:00
|
|
|
models = [
|
|
|
|
cs.ModelUsers,
|
|
|
|
cs.ModelSubscribers,
|
|
|
|
cs.ModelLists,
|
|
|
|
cs.ModelCampaigns,
|
|
|
|
cs.ModelTemplates
|
|
|
|
]
|
|
|
|
|
|
|
|
state = {
|
|
|
|
// Initialize empty states.
|
|
|
|
reqStates: this.models.reduce(
|
|
|
|
// eslint-disable-next-line
|
|
|
|
(map, obj) => ((map[obj] = cs.StatePending), map),
|
|
|
|
{}
|
|
|
|
),
|
|
|
|
// eslint-disable-next-line
|
|
|
|
data: this.models.reduce((map, obj) => ((map[obj] = []), map), {}),
|
|
|
|
modStates: {}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount = () => {
|
|
|
|
axios.defaults.paramsSerializer = params => {
|
|
|
|
return qs.stringify(params, { arrayFormat: "repeat" })
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
2019-03-09 08:46:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// modelRequest is an opinionated wrapper for model specific HTTP requests,
|
|
|
|
// including setting model states.
|
|
|
|
modelRequest = async (model, route, method, params) => {
|
|
|
|
let url = replaceParams(route, params)
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
reqStates: { ...this.state.reqStates, [model]: cs.StatePending }
|
|
|
|
})
|
|
|
|
try {
|
|
|
|
let req = {
|
|
|
|
method: method,
|
|
|
|
url: url
|
|
|
|
}
|
|
|
|
|
|
|
|
if (method === cs.MethodGet || method === cs.MethodDelete) {
|
|
|
|
req.params = params ? params : {}
|
|
|
|
} else {
|
|
|
|
req.data = params ? params : {}
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = await axios(req)
|
|
|
|
this.setState({
|
|
|
|
reqStates: { ...this.state.reqStates, [model]: cs.StateDone }
|
|
|
|
})
|
|
|
|
|
|
|
|
// If it's a GET call, set the response as the data state.
|
|
|
|
if (method === cs.MethodGet) {
|
|
|
|
this.setState({ data: { ...this.state.data, [model]: res.data.data } })
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
} catch (e) {
|
|
|
|
// If it's a GET call, throw a global notification.
|
|
|
|
if (method === cs.MethodGet) {
|
|
|
|
notification["error"]({
|
|
|
|
placement: cs.MsgPosition,
|
|
|
|
message: "Error fetching data",
|
|
|
|
description: Utils.HttpError(e).message
|
|
|
|
})
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
// Set states and show the error on the layout.
|
|
|
|
this.setState({
|
|
|
|
reqStates: { ...this.state.reqStates, [model]: cs.StateDone }
|
|
|
|
})
|
|
|
|
throw Utils.HttpError(e)
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
2019-03-09 08:46:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// request is a wrapper for generic HTTP requests.
|
|
|
|
request = async (url, method, params, headers) => {
|
|
|
|
url = replaceParams(url, params)
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
reqStates: { ...this.state.reqStates, [url]: cs.StatePending }
|
|
|
|
})
|
|
|
|
try {
|
|
|
|
let req = {
|
|
|
|
method: method,
|
|
|
|
url: url,
|
|
|
|
headers: headers ? headers : {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (method === cs.MethodGet || method === cs.MethodDelete) {
|
|
|
|
req.params = params ? params : {}
|
|
|
|
} else {
|
|
|
|
req.data = params ? params : {}
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = await axios(req)
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
reqStates: { ...this.state.reqStates, [url]: cs.StateDone }
|
|
|
|
})
|
|
|
|
return res
|
|
|
|
} catch (e) {
|
|
|
|
this.setState({
|
|
|
|
reqStates: { ...this.state.reqStates, [url]: cs.StateDone }
|
|
|
|
})
|
|
|
|
throw Utils.HttpError(e)
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
2019-03-09 08:46:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pageTitle = title => {
|
|
|
|
document.title = title
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
if (!window.CONFIG) {
|
|
|
|
return (
|
|
|
|
<div className="broken">
|
|
|
|
<p>
|
|
|
|
<img src={logo} alt="listmonk logo" />
|
|
|
|
</p>
|
|
|
|
<hr />
|
|
|
|
|
|
|
|
<h1>
|
|
|
|
<Icon type="warning" /> Something's not right
|
|
|
|
</h1>
|
|
|
|
<p>
|
|
|
|
The app configuration could not be loaded. Please ensure that the
|
|
|
|
app is running and then refresh this page.
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
)
|
2018-11-03 13:23:22 +01:00
|
|
|
}
|
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
return (
|
|
|
|
<BrowserRouter>
|
|
|
|
<Layout
|
|
|
|
modelRequest={this.modelRequest}
|
|
|
|
request={this.request}
|
|
|
|
reqStates={this.state.reqStates}
|
|
|
|
pageTitle={this.pageTitle}
|
|
|
|
config={window.CONFIG}
|
|
|
|
data={this.state.data}
|
|
|
|
/>
|
|
|
|
</BrowserRouter>
|
|
|
|
)
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
function replaceParams(route, params) {
|
|
|
|
// Replace :params in the URL with params in the array.
|
|
|
|
let uriParams = route.match(/:([a-z0-9\-_]+)/gi)
|
|
|
|
if (uriParams && uriParams.length > 0) {
|
|
|
|
uriParams.forEach(p => {
|
|
|
|
let pName = p.slice(1) // Lose the ":" prefix
|
|
|
|
if (params && params.hasOwnProperty(pName)) {
|
|
|
|
route = route.replace(p, params[pName])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return route
|
2018-10-29 10:46:53 +01:00
|
|
|
}
|
|
|
|
|
2018-10-25 15:51:47 +02:00
|
|
|
export default App
|