alot of settings stuff

This commit is contained in:
Roman Hergenreder 2020-06-26 17:24:57 +02:00
parent 09475be545
commit f5a8f2c777
5 changed files with 2578 additions and 250 deletions

@ -179,7 +179,7 @@ class CreateDatabase {
private static function MessageAcceptInvite() : string {
return str_replace("\n", "", intendCode(
"Hello {{username}},<br>
You were invited to create an account on {{site_name}}. Please click on the following link to
You were invited to create an account on {{site_name}}. Please click on the following link to
confirm your email address and complete your registration by choosing a new password.
If you want to decline the invitation, you can simply ignore this email. The link is valid for the next 48 hours:<br><br>
<a href=\"{{link}}\">{{link}}</a><br><br>
@ -191,7 +191,7 @@ class CreateDatabase {
private static function MessageResetPassword() : string {
return str_replace("\n", "", intendCode(
"Hello {{username}},<br>
you requested a password reset on {{sitename}}. Please click on the following link to
you requested a password reset on {{sitename}}. Please click on the following link to
choose a new password. If this request was not intended, you can simply ignore the email. The Link is valid for one hour:<br><br>
<a href=\"{{link}}\">{{link}}</a><br><br>
Best Regards<br>

2106
js/admin.min.js vendored

File diff suppressed because one or more lines are too long

90
src/package-lock.json generated

@ -4678,6 +4678,56 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"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": {
"version": "0.1.1",
"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": {
"version": "3.5.2",
"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": {
"version": "4.0.0-beta.11",
"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",
"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": {
"version": "2.1.0",
"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",
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@ -10855,6 +10928,18 @@
"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": {
"version": "6.0.7",
"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",
"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": {
"version": "1.0.4",
"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,
"dependencies": {
"chart.js": "^2.9.3",
"draft-js": "^0.11.6",
"draftjs-to-html": "^0.9.1",
"html-to-draftjs": "latest",
"moment": "^2.26.0",
"rc-color-picker": "^1.2.6",
"react": "^16.13.1",
"react-chartjs-2": "^2.9.0",
"react-collapse": "^5.0.1",
"react-dom": "^16.13.1",
"react-draft-wysiwyg": "^1.14.5",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.1",
"react-select": "^3.1.0",

@ -3,6 +3,11 @@ import {Link} from "react-router-dom";
import Alert from "../elements/alert";
import {Collapse} from "react-collapse/lib/Collapse";
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 {
@ -11,114 +16,114 @@ export default class Settings extends React.Component {
this.state = {
errors: [],
mailErrors: [],
generalErrors: [],
etcErrors: [],
settings: {},
generalOpened: true,
mailOpened: true,
etcOpened: true,
isResetting: false,
isSaving: false,
isSending: false,
test_email: "",
unsavedMailSettings: false
general: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
keys: ["site_name", "base_url", "user_registration_enabled"]
},
mail: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
isSending: false,
test_email: "",
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 = {
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() {
this.parent.api.getSettings().then((res) => {
if (res.success) {
this.setState({...this.state, settings: res.settings});
this.setState({ ...this.state, settings: res.settings });
} else {
let errors = this.state.errors.slice();
errors.push({title: "Error fetching settings", message: res.msg});
this.setState({...this.state, errors: errors});
this.setState({...this.state, errors: errors });
}
});
}
removeError(key, i) {
if (i >= 0 && i < this.state[key].length) {
let errors = this.state[key].slice();
errors.splice(i, 1);
this.setState({...this.state, [key]: errors});
removeError(i, category = null) {
if (category) {
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);
this.setState({...this.state, errors: errors});
}
}
}
toggleCollapse(key) {
this.setState({...this.state, [key]: !this.state[key]});
toggleCollapse(category) {
this.setState({...this.state, [category]: {...this.state[category], isOpen: !this.state[category].isOpen}});
}
getGeneralCard() {
createCard(category, color, icon, title, content) {
let errors = [];
for (let i = 0; i < this.state.generalErrors.length; i++) {
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError("generalErrors", i)} {...this.state.generalErrors[i]}/>)
let alerts = [];
for (let i = 0; i < this.state[category].alerts.length; 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"}}
onClick={() => this.toggleCollapse("generalOpened")}>
onClick={() => this.toggleCollapse(category)}>
<h4 className={"card-title"}>
<Icon className={"mr-2"} icon={"cogs"}/>
General Settings
<Icon className={"mr-2"} icon={icon}/>
{title}
</h4>
<div className={"card-tools"}>
<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>
</div>
</div>
<Collapse isOpened={this.state.generalOpened}>
<Collapse isOpened={this.state[category].isOpen}>
<div className={"card-body"}>
<div className={"row"}>
<div className={"col-12 col-lg-6"}>
{errors}
<div className={"form-group"}>
<label htmlFor={"site_name"}>Site Name</label>
<input type={"text"} className={"form-control"}
value={this.state.settings["site_name"] ?? ""}
placeholder={"Enter a title"} name={"site_name"} id={"site_name"}
onChange={this.onChangeValue.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"base_url"}>Base URL</label>
<input type={"text"} className={"form-control"}
value={this.state.settings["base_url"] ?? ""}
placeholder={"Enter a url"} name={"base_url"} id={"base_url"}
onChange={this.onChangeValue.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"user_registration_enabled"}>User Registration</label>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"user_registration_enabled"}
id={"user_registration_enabled"}
defaultChecked={(this.state.settings["user_registration_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"}
htmlFor={"user_registration_enabled"}>
Allow anyone to register an account
</label>
</div>
</div>
{alerts}
{content}
<div>
<button className={"btn btn-secondary ml-2"} onClick={() => this.onReset("generalErrors", this.generalKeys)}
disabled={this.state.isResetting || this.state.isSaving}>
{this.state.isResetting ?
<button className={"btn btn-secondary"} onClick={() => this.onReset(category)}
disabled={this.state[category].isResetting || this.state[category].isSaving}>
{this.state[category].isResetting ?
<span>Resetting&nbsp;<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 ?
<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&nbsp;<Icon icon={"circle-notch"}/></span> : "Save"}
</button>
</div>
@ -126,163 +131,209 @@ export default class Settings extends React.Component {
</div>
</div>
</Collapse>
</div>
}
createGeneralForm() {
return <>
<div className={"form-group"}>
<label htmlFor={"site_name"}>Site Name</label>
<input type={"text"} className={"form-control"}
value={this.state.settings["site_name"] ?? ""}
placeholder={"Enter a title"} name={"site_name"} id={"site_name"}
onChange={this.onChangeValue.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"base_url"}>Base URL</label>
<input type={"text"} className={"form-control"}
value={this.state.settings["base_url"] ?? ""}
placeholder={"Enter a url"} name={"base_url"} id={"base_url"}
onChange={this.onChangeValue.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"user_registration_enabled"}>User Registration</label>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"user_registration_enabled"}
id={"user_registration_enabled"}
checked={(this.state.settings["user_registration_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"}
htmlFor={"user_registration_enabled"}>
Allow anyone to register an account
</label>
</div>
</div>
</>
}
getEmailCard() {
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]}/>)
}
createMailForm() {
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 className={"form-group mt-2"}>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"mail_enabled"} id={"mail_enabled"}
checked={(this.state.settings["mail_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"} htmlFor={"mail_enabled"}>
Enable E-Mail service
</label>
</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-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"mail_enabled"} id={"mail_enabled"}
checked={(this.state.settings["mail_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"} htmlFor={"mail_enabled"}>
Enable E-Mail service
</label>
</div>
<hr className={"m-3"}/>
<label htmlFor={"mail_username"}>Username</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<hr className={"m-3"}/>
<label htmlFor={"mail_username"}>Username</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"hashtag"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["mail_username"] ?? ""}
placeholder={"Enter a username"} name={"mail_username"}
id={"mail_username"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"mail_password"} className={"mt-2"}>Password</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["mail_username"] ?? ""}
placeholder={"Enter a username"} name={"mail_username"}
id={"mail_username"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"mail_password"} className={"mt-2"}>Password</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"key"}/>
</span>
</div>
<input type={"password"} className={"form-control"}
value={this.state.settings["mail_password"] ?? ""}
placeholder={"(unchanged)"} name={"mail_password"}
id={"mail_password"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"mail_from"} className={"mt-2"}>Sender Email Address</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>@</span>
</div>
<input type={"email"} className={"form-control"}
value={this.state.settings["mail_from"] ?? ""}
placeholder={"Enter a email address"} name={"mail_from"}
id={"mail_from"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<div className={"row"}>
<div className={"col-6"}>
<label htmlFor={"mail_host"} className={"mt-2"}>SMTP Host</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"project-diagram"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["mail_host"] ?? ""}
placeholder={"e.g. smtp.example.com"} name={"mail_host"}
id={"mail_host"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
</div>
<div className={"col-6"}>
<label htmlFor={"mail_port"} className={"mt-2"}>SMTP Port</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"project-diagram"}/>
</span>
</div>
<input type={"number"} className={"form-control"}
value={parseInt(this.state.settings["mail_port"] ?? "25")}
placeholder={"smtp port"} name={"mail_port"}
id={"mail_port"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</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&nbsp;<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&nbsp;<Icon icon={"circle-notch"}/></span> : "Save"}
</button>
</div>
<div className={"mt-3"}>
<label htmlFor={"mail_from"} className={"mt-2"}>Send Test E-Mail</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>@</span>
</div>
<input type={"email"} className={"form-control"}
value={this.state.test_email}
placeholder={"Enter a email address"}
onChange={(e) => this.setState({
...this.state,
test_email: e.target.value
})}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<div className={"form-group form-inline mt-3"}>
<button className={"btn btn-info col-2"}
onClick={() => this.onSendTestMail()}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1" || this.state.isSending}>
{this.state.isSending ?
<span>Sending&nbsp;<Icon icon={"circle-notch"}/></span> : "Send Mail"}
</button>
<div className={"col-10"}>
{ this.state.unsavedMailSettings ? <span className={"text-red"}>You need to save your mail settings first.</span> : null }
</div>
</div>
</div>
</div>
<input type={"password"} className={"form-control"}
value={this.state.settings["mail_password"] ?? ""}
placeholder={"(unchanged)"} name={"mail_password"}
id={"mail_password"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"mail_from"} className={"mt-2"}>Sender Email Address</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>@</span>
</div>
<input type={"email"} className={"form-control"}
value={this.state.settings["mail_from"] ?? ""}
placeholder={"Enter a email address"} name={"mail_from"}
id={"mail_from"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<div className={"row"}>
<div className={"col-6"}>
<label htmlFor={"mail_host"} className={"mt-2"}>SMTP Host</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"project-diagram"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["mail_host"] ?? ""}
placeholder={"e.g. smtp.example.com"} name={"mail_host"}
id={"mail_host"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
</div>
</Collapse>
<div className={"col-6"}>
<label htmlFor={"mail_port"} className={"mt-2"}>SMTP Port</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"project-diagram"}/>
</span>
</div>
<input type={"number"} className={"form-control"}
value={parseInt(this.state.settings["mail_port"] ?? "25")}
placeholder={"smtp port"} name={"mail_port"}
id={"mail_port"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
</div>
</div>
<div className={"mt-3"}>
<label htmlFor={"mail_from"} className={"mt-2"}>Send Test E-Mail</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>@</span>
</div>
<input type={"email"} className={"form-control"}
value={this.state.mail.test_email}
placeholder={"Enter a email address"}
onChange={(e) => this.setState({
...this.state,
mail: {...this.state.mail, test_email: e.target.value},
})}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1"}/>
</div>
<div className={"form-group form-inline mt-3"}>
<button className={"btn btn-info col-2"}
onClick={() => this.onSendTestMail()}
disabled={(this.state.settings["mail_enabled"] ?? "0") !== "1" || this.state.mail.isSending}>
{this.state.mail.isSending ?
<span>Sending&nbsp;<Icon icon={"circle-notch"}/></span> : "Send Mail"}
</button>
<div className={"col-10"}>
{this.state.mail.unsavedMailSettings ?
<span className={"text-red"}>You need to save your mail settings first.</span> : null}
</div>
</div>
</div>
</>
}
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() {
let keys = [];
@ -351,12 +402,26 @@ export default class Settings extends React.Component {
</Collapse>
</>
}
*/
render() {
let errors = [];
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 <>
@ -378,20 +443,22 @@ export default class Settings extends React.Component {
<div className={"content"}>
{errors}
<div>
<div className={"card card-primary"}>
{this.getGeneralCard()}
</div>
<div className={"card card-warning"}>
{this.getEmailCard()}
</div>
<div className={"card card-secondary"}>
{this.getUncategorisedCard()}
</div>
{cards}
</div>
</div>
</>
}
onEditorStateChange(editorState, key) {
this.setState({
...this.state,
messages: {
...this.state.messages,
editor: editorState
}
});
};
onChangeValue(event) {
const target = event.target;
const name = target.name;
@ -403,45 +470,65 @@ export default class Settings extends React.Component {
}
let changedMailSettings = false;
if (name.startsWith("mail_")) {
if (this.state.mail.keys.includes(name)) {
changedMailSettings = true;
}
this.setState({...this.state, settings: {...this.state.settings, [name]: value},
unsavedMailSettings: changedMailSettings ? true : this.state.unsavedMailSettings
});
let newState = {...this.state, settings: {...this.state.settings, [name]: value}};
if (changedMailSettings) {
newState.mail = {...this.state.mail, unsavedMailSettings: true};
}
this.setState(newState);
}
onReset(errorKey, keys) {
this.setState({...this.state, isResetting: true});
onReset(category) {
this.setState({...this.state, [category]: {...this.state[category], isResetting: true}});
let values = {};
for (let key of keys) {
for (let key of this.state[category].keys) {
values[key] = this.state.settings[key];
}
let mailSettingsSaved = errorKey === "mailErrors";
this.parent.api.getSettings().then((res) => {
if (!res.success) {
let errors = this.state[errorKey].slice();
errors.push({title: "Error fetching settings", message: res.msg});
this.setState({...this.state, [errorKey]: errors, isResetting: false});
let alerts = this.state[category].alerts.slice();
alerts.push({title: "Error fetching settings", message: res.msg});
this.setState({
...this.state,
[category]: {...this.state[category], alerts: alerts, isResetting: false}
});
} else {
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] ?? "";
}
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) {
this.setState({...this.state, isSaving: true});
onSave(category) {
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 = {};
for (let key of keys) {
for (let key of this.state[category].keys) {
if (key === "mail_password" && !this.state.settings[key]) {
continue;
}
@ -449,34 +536,77 @@ export default class Settings extends React.Component {
values[key] = this.state.settings[key];
}
let mailSettingsSaved = errorKey === "mailErrors";
this.parent.api.saveSettings(values).then((res) => {
let alerts = this.state[category].alerts.slice();
let categoryUpdated = {...this.state[category], isSaving: false};
if (!res.success) {
let errors = this.state[errorKey].slice();
errors.push({title: "Error fetching settings", message: res.msg});
this.setState({...this.state, [errorKey]: errors, isSaving: false});
alerts.push({title: "Error fetching settings", message: res.msg});
} 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() {
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) => {
let errors = this.state.mailErrors.slice();
console.log(this.state.mail);
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) {
errors.push({title: "Error sending email", message: res.msg});
this.setState({...this.state, mailErrors: errors, isSending: false});
alerts.push({title: "Error sending email", message: res.msg});
} else {
errors.push({
alerts.push({
title: "Success!",
message: "E-Mail was successfully sent, check your inbox.",
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
}
});
}
}
}