listmonk/frontend/my/src/App.js

192 lines
5.0 KiB
JavaScript
Raw Normal View History

2019-03-09 08:46:47 +01:00
import React from "react"
import Utils from "./utils"
import { BrowserRouter } from "react-router-dom"
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>
)
}
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-25 15:51:47 +02:00
export default App