alot of settings stuff
This commit is contained in:
parent
09475be545
commit
f5a8f2c777
2106
js/admin.min.js
vendored
2106
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
90
src/package-lock.json
generated
90
src/package-lock.json
generated
@ -4678,6 +4678,56 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
||||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
|
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
|
||||||
},
|
},
|
||||||
|
"draft-js": {
|
||||||
|
"version": "0.11.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.6.tgz",
|
||||||
|
"integrity": "sha512-H8OaophZecxm0L/C0LvOo6IZrbMb+s0txPXF7DW3E2oUjSo5ufvWvWb3B/0K2wRqBnwJrRxApqljD899i0fZMA==",
|
||||||
|
"requires": {
|
||||||
|
"fbjs": "^1.0.0",
|
||||||
|
"immutable": "~3.7.4",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.6.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
|
||||||
|
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
|
||||||
|
},
|
||||||
|
"fbjs": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^2.4.1",
|
||||||
|
"fbjs-css-vars": "^1.0.0",
|
||||||
|
"isomorphic-fetch": "^2.1.1",
|
||||||
|
"loose-envify": "^1.0.0",
|
||||||
|
"object-assign": "^4.1.0",
|
||||||
|
"promise": "^7.1.1",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
|
"ua-parser-js": "^0.7.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"promise": {
|
||||||
|
"version": "7.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"requires": {
|
||||||
|
"asap": "~2.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draftjs-to-html": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-to-html/-/draftjs-to-html-0.9.1.tgz",
|
||||||
|
"integrity": "sha512-fFstE6+IayaVFBEvaFt/wN8vdj8FsTRzij7dy7LI9QIwf5LgfHFi9zSpvCg+feJ2tbYVqHxUkjcibwpsTpgFVQ=="
|
||||||
|
},
|
||||||
|
"draftjs-utils": {
|
||||||
|
"version": "0.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
|
||||||
|
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg=="
|
||||||
|
},
|
||||||
"duplexer": {
|
"duplexer": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||||
@ -5723,6 +5773,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fbjs-css-vars": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
|
||||||
|
},
|
||||||
"figgy-pudding": {
|
"figgy-pudding": {
|
||||||
"version": "3.5.2",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
||||||
@ -6476,6 +6531,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"html-to-draftjs": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ=="
|
||||||
|
},
|
||||||
"html-webpack-plugin": {
|
"html-webpack-plugin": {
|
||||||
"version": "4.0.0-beta.11",
|
"version": "4.0.0-beta.11",
|
||||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz",
|
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz",
|
||||||
@ -6629,6 +6689,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz",
|
||||||
"integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg=="
|
"integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg=="
|
||||||
},
|
},
|
||||||
|
"immutable": {
|
||||||
|
"version": "3.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
|
||||||
|
"integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks="
|
||||||
|
},
|
||||||
"import-cwd": {
|
"import-cwd": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||||
@ -7895,6 +7960,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
|
||||||
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
|
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
|
||||||
},
|
},
|
||||||
|
"linkify-it": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||||
|
"requires": {
|
||||||
|
"uc.micro": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"load-json-file": {
|
"load-json-file": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
|
||||||
@ -10855,6 +10928,18 @@
|
|||||||
"scheduler": "^0.19.1"
|
"scheduler": "^0.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-draft-wysiwyg": {
|
||||||
|
"version": "1.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.14.5.tgz",
|
||||||
|
"integrity": "sha512-utbJEs91757QXYoBwKRb/4kB3JdswLlj0heUiAeXs/OxZAUISJXxLMFLBIixRlIcUnNkwxOsMikRshDMtWIS3g==",
|
||||||
|
"requires": {
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"draftjs-utils": "^0.10.2",
|
||||||
|
"html-to-draftjs": "^1.5.0",
|
||||||
|
"linkify-it": "^2.2.0",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-error-overlay": {
|
"react-error-overlay": {
|
||||||
"version": "6.0.7",
|
"version": "6.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
||||||
@ -13137,6 +13222,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
||||||
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
|
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
|
||||||
},
|
},
|
||||||
|
"uc.micro": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
|
||||||
|
},
|
||||||
"unicode-canonical-property-names-ecmascript": {
|
"unicode-canonical-property-names-ecmascript": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||||
|
@ -4,12 +4,16 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
|
"draft-js": "^0.11.6",
|
||||||
|
"draftjs-to-html": "^0.9.1",
|
||||||
|
"html-to-draftjs": "latest",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
"rc-color-picker": "^1.2.6",
|
"rc-color-picker": "^1.2.6",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chartjs-2": "^2.9.0",
|
"react-chartjs-2": "^2.9.0",
|
||||||
"react-collapse": "^5.0.1",
|
"react-collapse": "^5.0.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
"react-draft-wysiwyg": "^1.14.5",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "^3.4.1",
|
"react-scripts": "^3.4.1",
|
||||||
"react-select": "^3.1.0",
|
"react-select": "^3.1.0",
|
||||||
|
@ -3,6 +3,11 @@ import {Link} from "react-router-dom";
|
|||||||
import Alert from "../elements/alert";
|
import Alert from "../elements/alert";
|
||||||
import {Collapse} from "react-collapse/lib/Collapse";
|
import {Collapse} from "react-collapse/lib/Collapse";
|
||||||
import Icon from "../elements/icon";
|
import Icon from "../elements/icon";
|
||||||
|
import { EditorState, ContentState, convertFromHTML, convertToRaw } from 'draft-js'
|
||||||
|
import { Editor } from 'react-draft-wysiwyg'
|
||||||
|
import draftToHtml from 'draftjs-to-html';
|
||||||
|
import htmlToDraft from 'html-to-draftjs';
|
||||||
|
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
|
||||||
|
|
||||||
export default class Settings extends React.Component {
|
export default class Settings extends React.Component {
|
||||||
|
|
||||||
@ -11,26 +16,44 @@ export default class Settings extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
errors: [],
|
errors: [],
|
||||||
mailErrors: [],
|
|
||||||
generalErrors: [],
|
|
||||||
etcErrors: [],
|
|
||||||
settings: {},
|
settings: {},
|
||||||
generalOpened: true,
|
general: {
|
||||||
mailOpened: true,
|
alerts: [],
|
||||||
etcOpened: true,
|
isOpen: true,
|
||||||
isResetting: false,
|
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
isResetting: false,
|
||||||
|
keys: ["site_name", "base_url", "user_registration_enabled"]
|
||||||
|
},
|
||||||
|
mail: {
|
||||||
|
alerts: [],
|
||||||
|
isOpen: true,
|
||||||
|
isSaving: false,
|
||||||
|
isResetting: false,
|
||||||
isSending: false,
|
isSending: false,
|
||||||
test_email: "",
|
test_email: "",
|
||||||
unsavedMailSettings: false
|
unsavedMailSettings: false,
|
||||||
|
keys: ["mail_enabled", "mail_host", "mail_port", "mail_username", "mail_password", "mail_from"]
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
alerts: [],
|
||||||
|
isOpen: true,
|
||||||
|
isSaving: false,
|
||||||
|
isResetting: false,
|
||||||
|
editor: EditorState.createEmpty(),
|
||||||
|
isEditing: null,
|
||||||
|
keys: ["message_confirm_email", "message_accept_invite", "message_reset_password"]
|
||||||
|
},
|
||||||
|
uncategorised: {
|
||||||
|
alerts: [],
|
||||||
|
isOpen: true,
|
||||||
|
isSaving: false,
|
||||||
|
isResetting: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.parent = {
|
this.parent = {
|
||||||
api: props.api
|
api: props.api
|
||||||
};
|
};
|
||||||
|
|
||||||
this.mailKeys = ["mail_enabled", "mail_host", "mail_port", "mail_username", "mail_password", "mail_from"];
|
|
||||||
this.generalKeys = ["site_name", "base_url", "user_registration_enabled"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -45,43 +68,74 @@ export default class Settings extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeError(key, i) {
|
removeError(i, category = null) {
|
||||||
if (i >= 0 && i < this.state[key].length) {
|
if (category) {
|
||||||
let errors = this.state[key].slice();
|
if (i >= 0 && i < this.state[category].alerts.length) {
|
||||||
|
let alerts = this.state[category].alerts.slice();
|
||||||
|
alerts.splice(i, 1);
|
||||||
|
this.setState({...this.state, [category]: {...this.state[category], alerts: alerts}});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (i >= 0 && i < this.state.errors.length) {
|
||||||
|
let errors = this.state.errors.slice();
|
||||||
errors.splice(i, 1);
|
errors.splice(i, 1);
|
||||||
this.setState({...this.state, [key]: errors});
|
this.setState({...this.state, errors: errors});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapse(key) {
|
toggleCollapse(category) {
|
||||||
this.setState({...this.state, [key]: !this.state[key]});
|
this.setState({...this.state, [category]: {...this.state[category], isOpen: !this.state[category].isOpen}});
|
||||||
}
|
}
|
||||||
|
|
||||||
getGeneralCard() {
|
createCard(category, color, icon, title, content) {
|
||||||
|
|
||||||
let errors = [];
|
let alerts = [];
|
||||||
for (let i = 0; i < this.state.generalErrors.length; i++) {
|
for (let i = 0; i < this.state[category].alerts.length; i++) {
|
||||||
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("generalErrors", i)} {...this.state.generalErrors[i]}/>)
|
alerts.push(<Alert key={"alert-" + i}
|
||||||
|
onClose={() => this.removeError(i, category)} {...this.state[category].alerts[i]}/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <div className={"card card-" + color} key={"card-" + category}>
|
||||||
<div className={"card-header"} style={{cursor: "pointer"}}
|
<div className={"card-header"} style={{cursor: "pointer"}}
|
||||||
onClick={() => this.toggleCollapse("generalOpened")}>
|
onClick={() => this.toggleCollapse(category)}>
|
||||||
<h4 className={"card-title"}>
|
<h4 className={"card-title"}>
|
||||||
<Icon className={"mr-2"} icon={"cogs"}/>
|
<Icon className={"mr-2"} icon={icon}/>
|
||||||
General Settings
|
{title}
|
||||||
</h4>
|
</h4>
|
||||||
<div className={"card-tools"}>
|
<div className={"card-tools"}>
|
||||||
<span className={"btn btn-tool btn-sm"}>
|
<span className={"btn btn-tool btn-sm"}>
|
||||||
<Icon icon={this.state.generalOpened ? "angle-up" : "angle-down"}/>
|
<Icon icon={this.state[category].isOpen ? "angle-up" : "angle-down"}/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Collapse isOpened={this.state.generalOpened}>
|
<Collapse isOpened={this.state[category].isOpen}>
|
||||||
<div className={"card-body"}>
|
<div className={"card-body"}>
|
||||||
<div className={"row"}>
|
<div className={"row"}>
|
||||||
<div className={"col-12 col-lg-6"}>
|
<div className={"col-12 col-lg-6"}>
|
||||||
{errors}
|
{alerts}
|
||||||
|
{content}
|
||||||
|
<div>
|
||||||
|
<button className={"btn btn-secondary"} onClick={() => this.onReset(category)}
|
||||||
|
disabled={this.state[category].isResetting || this.state[category].isSaving}>
|
||||||
|
{this.state[category].isResetting ?
|
||||||
|
<span>Resetting <Icon icon={"circle-notch"}/></span> : "Reset"}
|
||||||
|
</button>
|
||||||
|
<button className={"btn btn-success ml-2"} onClick={() => this.onSave(category)}
|
||||||
|
disabled={this.state[category].isResetting || this.state[category].isSaving}>
|
||||||
|
{this.state[category].isSaving ?
|
||||||
|
<span>Saving <Icon icon={"circle-notch"}/></span> : "Save"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
createGeneralForm() {
|
||||||
|
return <>
|
||||||
<div className={"form-group"}>
|
<div className={"form-group"}>
|
||||||
<label htmlFor={"site_name"}>Site Name</label>
|
<label htmlFor={"site_name"}>Site Name</label>
|
||||||
<input type={"text"} className={"form-control"}
|
<input type={"text"} className={"form-control"}
|
||||||
@ -102,7 +156,7 @@ export default class Settings extends React.Component {
|
|||||||
<input type={"checkbox"} className={"form-check-input"}
|
<input type={"checkbox"} className={"form-check-input"}
|
||||||
name={"user_registration_enabled"}
|
name={"user_registration_enabled"}
|
||||||
id={"user_registration_enabled"}
|
id={"user_registration_enabled"}
|
||||||
defaultChecked={(this.state.settings["user_registration_enabled"] ?? "0") === "1"}
|
checked={(this.state.settings["user_registration_enabled"] ?? "0") === "1"}
|
||||||
onChange={this.onChangeValue.bind(this)}/>
|
onChange={this.onChangeValue.bind(this)}/>
|
||||||
<label className={"form-check-label"}
|
<label className={"form-check-label"}
|
||||||
htmlFor={"user_registration_enabled"}>
|
htmlFor={"user_registration_enabled"}>
|
||||||
@ -110,50 +164,11 @@ export default class Settings extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<button className={"btn btn-secondary ml-2"} onClick={() => this.onReset("generalErrors", this.generalKeys)}
|
|
||||||
disabled={this.state.isResetting || this.state.isSaving}>
|
|
||||||
{this.state.isResetting ?
|
|
||||||
<span>Resetting <Icon icon={"circle-notch"}/></span> : "Reset"}
|
|
||||||
</button>
|
|
||||||
<button className={"btn btn-success ml-2"} onClick={() => this.onSave("generalErrors", this.generalKeys)}
|
|
||||||
disabled={this.state.isResetting || this.state.isSaving}>
|
|
||||||
{this.state.isSaving ?
|
|
||||||
<span>Saving <Icon icon={"circle-notch"}/></span> : "Save"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmailCard() {
|
createMailForm() {
|
||||||
|
|
||||||
let errors = [];
|
|
||||||
for (let i = 0; i < this.state.mailErrors.length; i++) {
|
|
||||||
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("mailErrors", i)} {...this.state.mailErrors[i]}/>)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className={"card-header"} style={{cursor: "pointer"}}
|
|
||||||
onClick={() => this.toggleCollapse("mailOpened")}>
|
|
||||||
<h4 className={"card-title"}>
|
|
||||||
<Icon className={"mr-2"} icon={"envelope"}/>
|
|
||||||
Mail Settings
|
|
||||||
</h4>
|
|
||||||
<div className={"card-tools"}>
|
|
||||||
<span className={"btn btn-tool btn-sm"}>
|
|
||||||
<Icon icon={this.state.mailOpened ? "angle-up" : "angle-down"}/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Collapse isOpened={this.state.mailOpened}>
|
|
||||||
<div className={"card-body"}>
|
|
||||||
<div className={"row"}>
|
|
||||||
<div className={"col-12 col-lg-6"}>
|
|
||||||
{errors}
|
|
||||||
<div className={"form-group mt-2"}>
|
<div className={"form-group mt-2"}>
|
||||||
<div className={"form-check"}>
|
<div className={"form-check"}>
|
||||||
<input type={"checkbox"} className={"form-check-input"}
|
<input type={"checkbox"} className={"form-check-input"}
|
||||||
@ -164,6 +179,7 @@ export default class Settings extends React.Component {
|
|||||||
Enable E-Mail service
|
Enable E-Mail service
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<hr className={"m-3"}/>
|
<hr className={"m-3"}/>
|
||||||
<label htmlFor={"mail_username"}>Username</label>
|
<label htmlFor={"mail_username"}>Username</label>
|
||||||
<div className={"input-group"}>
|
<div className={"input-group"}>
|
||||||
@ -234,21 +250,6 @@ export default class Settings extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button className={"btn btn-secondary ml-2"}
|
|
||||||
onClick={() => this.onReset("mailErrors", this.mailKeys)}
|
|
||||||
disabled={this.state.isResetting || this.state.isSaving}>
|
|
||||||
{this.state.isResetting ?
|
|
||||||
<span>Resetting <Icon icon={"circle-notch"}/></span> : "Reset"}
|
|
||||||
</button>
|
|
||||||
<button className={"btn btn-success ml-2"}
|
|
||||||
onClick={() => this.onSave("mailErrors", this.mailKeys)}
|
|
||||||
disabled={this.state.isResetting || this.state.isSaving}>
|
|
||||||
{this.state.isSaving ?
|
|
||||||
<span>Saving <Icon icon={"circle-notch"}/></span> : "Save"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className={"mt-3"}>
|
<div className={"mt-3"}>
|
||||||
<label htmlFor={"mail_from"} className={"mt-2"}>Send Test E-Mail</label>
|
<label htmlFor={"mail_from"} className={"mt-2"}>Send Test E-Mail</label>
|
||||||
<div className={"input-group"}>
|
<div className={"input-group"}>
|
||||||
@ -256,33 +257,83 @@ export default class Settings extends React.Component {
|
|||||||
<span className={"input-group-text"}>@</span>
|
<span className={"input-group-text"}>@</span>
|
||||||
</div>
|
</div>
|
||||||
<input type={"email"} className={"form-control"}
|
<input type={"email"} className={"form-control"}
|
||||||
value={this.state.test_email}
|
value={this.state.mail.test_email}
|
||||||
placeholder={"Enter a email address"}
|
placeholder={"Enter a email address"}
|
||||||
onChange={(e) => this.setState({
|
onChange={(e) => this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
test_email: e.target.value
|
mail: {...this.state.mail, test_email: e.target.value},
|
||||||
})}
|
})}
|
||||||
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
|
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
|
||||||
</div>
|
</div>
|
||||||
<div className={"form-group form-inline mt-3"}>
|
<div className={"form-group form-inline mt-3"}>
|
||||||
<button className={"btn btn-info col-2"}
|
<button className={"btn btn-info col-2"}
|
||||||
onClick={() => this.onSendTestMail()}
|
onClick={() => this.onSendTestMail()}
|
||||||
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1" || this.state.isSending}>
|
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1" || this.state.mail.isSending}>
|
||||||
{this.state.isSending ?
|
{this.state.mail.isSending ?
|
||||||
<span>Sending <Icon icon={"circle-notch"}/></span> : "Send Mail"}
|
<span>Sending <Icon icon={"circle-notch"}/></span> : "Send Mail"}
|
||||||
</button>
|
</button>
|
||||||
<div className={"col-10"}>
|
<div className={"col-10"}>
|
||||||
{ this.state.unsavedMailSettings ? <span className={"text-red"}>You need to save your mail settings first.</span> : null }
|
{this.state.mail.unsavedMailSettings ?
|
||||||
|
<span className={"text-red"}>You need to save your mail settings first.</span> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMessagesForm() {
|
||||||
|
|
||||||
|
const editor = <Editor
|
||||||
|
editorState={this.state.messages.editor}
|
||||||
|
onEditorStateChange={this.onEditorStateChange.bind(this)}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
let messageTemplates = {
|
||||||
|
"message_confirm_email": "Confirm E-Mail Message",
|
||||||
|
"message_accept_invite": "Accept Invitation Message",
|
||||||
|
"message_reset_password": "Reset Password Message",
|
||||||
|
};
|
||||||
|
|
||||||
|
let formGroups = [];
|
||||||
|
for (let key in messageTemplates) {
|
||||||
|
let title = messageTemplates[key];
|
||||||
|
if (this.state.messages.isEditing === key) {
|
||||||
|
formGroups.push(
|
||||||
|
<div className={"form-group"} key={"group-" + key}>
|
||||||
|
<label htmlFor={key}>
|
||||||
|
{ title }
|
||||||
|
<Icon icon={"times"} className={"ml-2 text-danger"} style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.closeEditor(false)}
|
||||||
|
/>
|
||||||
|
<Icon icon={"check"} className={"ml-1 text-success"} style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.closeEditor(true)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{ editor }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
formGroups.push(
|
||||||
|
<div className={"form-group"} key={"group-" + key}>
|
||||||
|
<label htmlFor={key}>
|
||||||
|
{ title }
|
||||||
|
<Icon icon={"pencil-alt"} className={"ml-2"} style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.openEditor(key)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<textarea rows={6} className={"form-control"} disabled={true}
|
||||||
|
value={this.state.settings[key] ?? ""} id={key} name={key}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
getUncategorisedCard() {
|
getUncategorisedCard() {
|
||||||
|
|
||||||
let keys = [];
|
let keys = [];
|
||||||
@ -351,12 +402,26 @@ export default class Settings extends React.Component {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
let errors = [];
|
let errors = [];
|
||||||
for (let i = 0; i < this.state.errors.length; i++) {
|
for (let i = 0; i < this.state.errors.length; i++) {
|
||||||
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("errors", i)} {...this.state.errors[i]}/>)
|
errors.push(<Alert key={"error-" + i}
|
||||||
|
onClose={() => this.removeError("errors", i)} {...this.state.errors[i]}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = {
|
||||||
|
"general": {color: "primary", icon: "cogs", title: "General Settings", content: this.createGeneralForm()},
|
||||||
|
"mail": {color: "warning", icon: "envelope", title: "Mail Settings", content: this.createMailForm()},
|
||||||
|
"messages": {color: "info", icon: "copy", title: "Message Templates", content: this.getMessagesForm()},
|
||||||
|
"uncategorised": {color: "secondary", icon: "stream", title: "Uncategorised", content: <></>},
|
||||||
|
};
|
||||||
|
|
||||||
|
let cards = [];
|
||||||
|
for (let name in categories) {
|
||||||
|
let category = categories[name];
|
||||||
|
cards.push(this.createCard(name, category.color, category.icon, category.title, category.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@ -378,20 +443,22 @@ export default class Settings extends React.Component {
|
|||||||
<div className={"content"}>
|
<div className={"content"}>
|
||||||
{errors}
|
{errors}
|
||||||
<div>
|
<div>
|
||||||
<div className={"card card-primary"}>
|
{cards}
|
||||||
{this.getGeneralCard()}
|
|
||||||
</div>
|
|
||||||
<div className={"card card-warning"}>
|
|
||||||
{this.getEmailCard()}
|
|
||||||
</div>
|
|
||||||
<div className={"card card-secondary"}>
|
|
||||||
{this.getUncategorisedCard()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEditorStateChange(editorState, key) {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
messages: {
|
||||||
|
...this.state.messages,
|
||||||
|
editor: editorState
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onChangeValue(event) {
|
onChangeValue(event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
const name = target.name;
|
const name = target.name;
|
||||||
@ -403,45 +470,65 @@ export default class Settings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let changedMailSettings = false;
|
let changedMailSettings = false;
|
||||||
if (name.startsWith("mail_")) {
|
if (this.state.mail.keys.includes(name)) {
|
||||||
changedMailSettings = true;
|
changedMailSettings = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({...this.state, settings: {...this.state.settings, [name]: value},
|
let newState = {...this.state, settings: {...this.state.settings, [name]: value}};
|
||||||
unsavedMailSettings: changedMailSettings ? true : this.state.unsavedMailSettings
|
if (changedMailSettings) {
|
||||||
});
|
newState.mail = {...this.state.mail, unsavedMailSettings: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
onReset(errorKey, keys) {
|
this.setState(newState);
|
||||||
this.setState({...this.state, isResetting: true});
|
}
|
||||||
|
|
||||||
|
onReset(category) {
|
||||||
|
this.setState({...this.state, [category]: {...this.state[category], isResetting: true}});
|
||||||
|
|
||||||
let values = {};
|
let values = {};
|
||||||
for (let key of keys) {
|
for (let key of this.state[category].keys) {
|
||||||
values[key] = this.state.settings[key];
|
values[key] = this.state.settings[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
let mailSettingsSaved = errorKey === "mailErrors";
|
|
||||||
this.parent.api.getSettings().then((res) => {
|
this.parent.api.getSettings().then((res) => {
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
let errors = this.state[errorKey].slice();
|
let alerts = this.state[category].alerts.slice();
|
||||||
errors.push({title: "Error fetching settings", message: res.msg});
|
alerts.push({title: "Error fetching settings", message: res.msg});
|
||||||
this.setState({...this.state, [errorKey]: errors, isResetting: false});
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
[category]: {...this.state[category], alerts: alerts, isResetting: false}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let newSettings = {...this.state.settings};
|
let newSettings = {...this.state.settings};
|
||||||
for (let key of keys) {
|
let categoryUpdated = {...this.state[category], isResetting: false};
|
||||||
|
|
||||||
|
for (let key of this.state[category].keys) {
|
||||||
newSettings[key] = res.settings[key] ?? "";
|
newSettings[key] = res.settings[key] ?? "";
|
||||||
}
|
}
|
||||||
this.setState({...this.state, settings: newSettings, isResetting: false,
|
|
||||||
unsavedMailSettings: mailSettingsSaved ? false : this.state.unsavedMailSettings});
|
if (category === "mail") {
|
||||||
|
categoryUpdated.unsavedMailSettings = false;
|
||||||
|
} else if (category === "messages") {
|
||||||
|
categoryUpdated.isEditing = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
...this.state, settings: newSettings,
|
||||||
|
[category]: categoryUpdated
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(errorKey, keys) {
|
onSave(category) {
|
||||||
this.setState({...this.state, isSaving: true});
|
this.setState({...this.state, [category]: {...this.state[category], isSaving: true}});
|
||||||
|
|
||||||
|
if (category === "messages" && this.state.messages.isEditing) {
|
||||||
|
this.closeEditor(true, () => this.onSave(category));
|
||||||
|
}
|
||||||
|
|
||||||
let values = {};
|
let values = {};
|
||||||
for (let key of keys) {
|
for (let key of this.state[category].keys) {
|
||||||
if (key === "mail_password" && !this.state.settings[key]) {
|
if (key === "mail_password" && !this.state.settings[key]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -449,34 +536,77 @@ export default class Settings extends React.Component {
|
|||||||
values[key] = this.state.settings[key];
|
values[key] = this.state.settings[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
let mailSettingsSaved = errorKey === "mailErrors";
|
|
||||||
this.parent.api.saveSettings(values).then((res) => {
|
this.parent.api.saveSettings(values).then((res) => {
|
||||||
|
let alerts = this.state[category].alerts.slice();
|
||||||
|
let categoryUpdated = {...this.state[category], isSaving: false};
|
||||||
|
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
let errors = this.state[errorKey].slice();
|
alerts.push({title: "Error fetching settings", message: res.msg});
|
||||||
errors.push({title: "Error fetching settings", message: res.msg});
|
|
||||||
this.setState({...this.state, [errorKey]: errors, isSaving: false});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({...this.state, isSaving: false, unsavedMailSettings: mailSettingsSaved ? false : this.state.unsavedMailSettings });
|
alerts.push({title: "Success", message: "Settings were successfully saved.", type: "success"});
|
||||||
|
if (category === "mail") categoryUpdated.unsavedMailSettings = false;
|
||||||
|
this.setState({...this.state, [category]: categoryUpdated});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
categoryUpdated.alerts = alerts;
|
||||||
|
this.setState({...this.state, [category]: categoryUpdated});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSendTestMail() {
|
onSendTestMail() {
|
||||||
this.setState({...this.state, isSending: true});
|
this.setState({...this.state, mail: {...this.state.mail, isSending: true}});
|
||||||
|
|
||||||
this.parent.api.sendTestMail(this.state.test_email).then((res) => {
|
console.log(this.state.mail);
|
||||||
let errors = this.state.mailErrors.slice();
|
this.parent.api.sendTestMail(this.state.mail.test_email).then((res) => {
|
||||||
|
let alerts = this.state.mail.alerts.slice();
|
||||||
|
let newState = {...this.state.mail, isSending: false};
|
||||||
if (!res.success) {
|
if (!res.success) {
|
||||||
errors.push({title: "Error sending email", message: res.msg});
|
alerts.push({title: "Error sending email", message: res.msg});
|
||||||
this.setState({...this.state, mailErrors: errors, isSending: false});
|
|
||||||
} else {
|
} else {
|
||||||
errors.push({
|
alerts.push({
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "E-Mail was successfully sent, check your inbox.",
|
message: "E-Mail was successfully sent, check your inbox.",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
this.setState({...this.state, mailErrors: errors, isSending: false, test_email: ""});
|
newState.test_email = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.alerts = alerts;
|
||||||
|
this.setState({...this.state, mail: newState});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEditor(save, callback = null) {
|
||||||
|
if (this.state.messages.isEditing) {
|
||||||
|
const key = this.state.messages.isEditing;
|
||||||
|
let newState = { ...this.state, messages: {...this.state.messages, isEditing: null }};
|
||||||
|
|
||||||
|
if (save) {
|
||||||
|
newState.settings = {
|
||||||
|
...this.state.settings,
|
||||||
|
[key]: draftToHtml(convertToRaw(this.state.messages.editor.getCurrentContent())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
callback = callback || function () { };
|
||||||
|
this.setState(newState, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openEditor(message) {
|
||||||
|
this.closeEditor(true);
|
||||||
|
const contentBlock = htmlToDraft(this.state.settings[message] ?? "");
|
||||||
|
if (contentBlock) {
|
||||||
|
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
|
||||||
|
const editorState = EditorState.createWithContent(contentState);
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
messages: {
|
||||||
|
...this.state.messages,
|
||||||
|
isEditing: message,
|
||||||
|
editor: editorState
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user