User Edit

This commit is contained in:
2020-06-23 20:57:54 +02:00
parent b6c726bad5
commit 9cbd129d4e
6 changed files with 226 additions and 34 deletions

View File

@@ -35,6 +35,10 @@ export default class API {
return data && data.success && data.loggedIn;
}
async editUser(id, username, email, password, groups) {
return this.apiCall("user/edit", { "id": id, "username": username, "email": email, "password": password, "groups": groups });
}
async logout() {
return this.apiCall("user/logout");
}

1
src/src/include/select2.min.css vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import Icon from "../elements/icon";
import Alert from "../elements/alert";
import {Link} from "react-router-dom";
import "../include/select2.min.css";
export default class EditUser extends React.Component {
@@ -13,33 +14,102 @@ export default class EditUser extends React.Component {
this.state = {
user: {},
errors: [],
alerts: [],
fetchError: null,
loaded: false,
isSaving: false
}
isSaving: false,
groups: { },
searchString: "",
searchActive: false
};
this.searchBox = React.createRef();
}
removeError(i) {
if (i >= 0 && i < this.state.errors.length) {
let errors = this.state.errors.slice();
errors.splice(i, 1);
this.setState({...this.state, errors: errors});
removeAlert(i) {
if (i >= 0 && i < this.state.alerts.length) {
let alerts = this.state.alerts.slice();
alerts.splice(i, 1);
this.setState({...this.state, alerts: alerts});
}
}
componentDidMount() {
this.parent.api.getUser(this.props.match.params["userId"]).then((res) => {
if (res.success) {
this.setState({ ...this.state, user: res.user, loaded: true });
this.setState({ ...this.state, user: {... res.user, password: ""} });
this.parent.api.fetchGroups(1, 50).then((res) => {
if (res.success) {
this.setState({ ...this.state, groups: res.groups, loaded: true });
} else {
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
}
});
} else {
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
}
});
}
render() {
onChangeInput(event) {
const target = event.target;
const value = target.value;
const name = target.name;
if (name === "search") {
this.setState({ ...this.state, searchString: value });
} else {
this.setState({ ...this.state, user: { ...this.state.user, [name]: value } });
}
}
onToggleSearch(e) {
e.stopPropagation();
this.setState({ ...this.state, searchActive: !this.state.searchActive });
this.searchBox.current.focus();
}
onSubmitForm(event) {
event.preventDefault();
event.stopPropagation();
const id = this.props.match.params["userId"];
const username = this.state.user["name"];
const email = this.state.user["email"];
let password = this.state.user["password"].length > 0 ? this.state.user["password"] : null;
let groups = Object.keys(this.state.user.groups);
this.setState({ ...this.state, isSaving: true});
this.parent.api.editUser(id, username, email, password, groups).then((res) => {
let alerts = this.state.alerts.slice();
if (res.success) {
alerts.push({ title: "Success", message: "User was successfully updated.", type: "success" });
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
} else {
alerts.push({ title: "Error updating user", message: res.msg, type: "danger" });
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
}
});
}
onRemoveGroup(event, groupId) {
event.stopPropagation();
if (this.state.user.groups.hasOwnProperty(groupId)) {
let groups = { ...this.state.user.groups };
delete groups[groupId];
this.setState({ ...this.state, user: { ...this.state.user, groups: groups }});
}
}
onAddGroup(event, groupId) {
event.stopPropagation();
if (!this.state.user.groups.hasOwnProperty(groupId)) {
let groups = { ...this.state.user.groups, [groupId]: { ...this.state.groups[groupId] } };
this.setState({ ...this.state, user: { ...this.state.user, groups: groups }, searchActive: false, searchString: "" });
}
}
render() {
if (!this.state.loaded) {
return <h2 className={"text-center"}>
Loading<br/>
@@ -47,31 +117,112 @@ export default class EditUser extends React.Component {
</h2>
}
let errors = [];
let alerts = [];
let form = null;
if(this.state.fetchError) {
errors.push(
<Alert key={"error-fetch"} title={"Error fetching user details"} type={"danger"} message={
alerts.push(
<Alert key={"error-fetch"} title={"Error fetching data"} type={"danger"} message={
<div>{this.state.fetchError}<br/>You can meanwhile return to the&nbsp;
<Link to={"/admin/users"}>user overview</Link>
</div>
}/>
)
} else {
for (let i = 0; i < this.state.errors.length; i++) {
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
for (let i = 0; i < this.state.alerts.length; i++) {
alerts.push(<Alert key={"error-" + i} onClose={() => this.removeAlert(i)} {...this.state.alerts[i]}/>)
}
form = <form role={"form"} onSubmit={(e) => e.preventDefault()}>
let possibleOptions = [];
let renderedOptions = [];
for (let groupId in this.state.groups) {
if (this.state.groups.hasOwnProperty(groupId)) {
let groupName = this.state.groups[groupId].name;
let groupColor = this.state.groups[groupId].color;
if (this.state.user.groups.hasOwnProperty(groupId)) {
renderedOptions.push(
<li className={"select2-selection__choice"} key={"group-" + groupId} title={groupName} style={{backgroundColor: groupColor}}>
<span className="select2-selection__choice__remove" role="presentation"
onClick={(e) => this.onRemoveGroup(e, groupId)}>
×
</span>
{groupName}
</li>
);
} else {
if (this.state.searchString.length === 0 || groupName.toLowerCase().includes(this.state.searchString.toLowerCase())) {
possibleOptions.push(
<li className={"select2-results__option"} role={"option"} key={"group-" + groupId} aria-selected={false}
onClick={(e) => this.onAddGroup(e, groupId)}>
{groupName}
</li>
);
}
}
}
}
let searchWidth = "100%";
let placeholder = "Select Groups";
let searchVisible = (this.state.searchString.length > 0 || this.state.searchActive) ? "block" : "none";
if (renderedOptions.length > 0) {
searchWidth = (0.75 + this.state.searchString.length * 0.75) + "em";
placeholder = "";
}
form = <form role={"form"} onSubmit={this.onSubmitForm.bind(this)}>
<div className={"form-group"}>
<label htmlFor={"username"}>Username</label>
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
name={"username"} id={"username"} maxLength={32} value={this.state.user.name}/>
name={"username"} id={"username"} maxLength={32} value={this.state.user.name}
onChange={this.onChangeInput.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"email"}>E-Mail</label>
<input type={"email"} className={"form-control"} placeholder={"E-Mail address"}
id={"email"} name={"email"} maxLength={64} value={this.state.user.email} />
id={"email"} name={"email"} maxLength={64} value={this.state.user.email}
onChange={this.onChangeInput.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"password"}>Password</label>
<input type={"password"} className={"form-control"} placeholder={"(unchanged)"}
id={"password"} name={"password"} value={this.state.user.password}
onChange={this.onChangeInput.bind(this)}/>
</div>
<div className={"form-group position-relative"}>
<label>Groups</label>
<span className={"select2 select2-container select2-container--default select2-container--below"}
dir={"ltr"} style={{width: "100%"}} >
<span className="selection">
<span className={"select2-selection select2-selection--multiple"} role={"combobox"} aria-haspopup={"true"}
aria-expanded={false} aria-disabled={false} onClick={this.onToggleSearch.bind(this)}>
<ul className={"select2-selection__rendered"}>
{renderedOptions}
<li className={"select2-search select2-search--inline"}>
<input className={"select2-search__field"} type={"search"} tabIndex={0}
autoComplete={"off"} autoCorrect={"off"} autoCapitalize={"none"} spellCheck={false}
role={"searchbox"} aria-autocomplete={"list"} placeholder={placeholder}
name={"search"} style={{width: searchWidth}} value={this.state.searchString}
onChange={this.onChangeInput.bind(this)} ref={this.searchBox} />
</li>
</ul>
</span>
</span>
<span className="dropdown-wrapper" aria-hidden="true"/>
</span>
<span className={"select2-container select2-container--default select2-container--open"}
style={{position: "absolute", bottom: 0, left: 0, width: "100%", display: searchVisible}}>
<span className={"select2-dropdown select2-dropdown--below"} dir={"ltr"}>
<span className={"select2-results"}>
<ul className={"select2-results__options"} role={"listbox"}
aria-multiselectable={true} aria-expanded={true} aria-hidden={false}>
{possibleOptions}
</ul>
</span>
</span>
</span>
</div>
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
@@ -105,7 +256,7 @@ export default class EditUser extends React.Component {
<div className={"content"}>
<div className={"row"}>
<div className={"col-lg-6 pl-5 pr-5"}>
{errors}
{alerts}
{form}
</div>
</div>