Source: pages/UserDetailPage.js

import React, {Component} from 'react';
import * as apiCalls from "../apiCalls/apiCalls";
import handleError from "../shared/failureHandler";
import Spinner from "../components/Spinner";
import Input from "../components/Input";
import ButtonWithProgress from "../components/ButtonWithProgress";
import PageContentContainer from "../components/PageContentContainer";
import Breadcrumbs from "../components/Breadcrumbs";
import BreadcrumbsLink from "../components/BreadcrumbsLink";
import PageInfo from "../components/PageInfo";

/**
 * page for viewing details about user
 */
class UserDetailPage extends Component {

    /**
     * current page state
     */
    state = {
        userId: this.props.match.params.userId,
        username: "",
        email: "",
        createdAt: null,
        isBanned: false,
        isTranslator: true,
        isInstitutionOwner: false,
        institutionName: "",
        pendingApiCall: true,
        pendingApiCallUsernameUpdate: false,
        pendingApiCallChangePassword: false,
        pendingApiCallTranslator: false,
        pendingApiCallBan: false,
        pendingApiCallRemoveInstitution: false,
        usernameUpdated: false,
        errors: {},
    }

    /**
     * called when page is mounted
     */
    componentDidMount() {
        this.setState({pendingApiCall: true})
        // get user details from server
        apiCalls.getUser(this.state.userId).then(response => {
            const {
                username,
                email,
                createdAt,
                isBanned,
                isTranslator,
                isInstitutionOwner,
                institutionName
            } = response.data;

            this.setState({username, email, createdAt, isBanned, isTranslator, isInstitutionOwner, institutionName, pendingApiCall: false});
        }).catch(error => {
            // handle unauthorized state
            return handleError(error);
        })
    }

    /**
     * called when text inputs are changed
     * @param event
     */
    onChange = (event) => {
        // delete errors and set new value
        const errors = {...this.state.errors};
        delete errors[event.target.name];
        this.setState({errors, usernameUpdated: false, [event.target.name]: event.target.value});
    }

    /**
     * handles error from http request
     * @param apiError error
     * @param apiCall api call name
     */
    handleApiError = (apiError, apiCall) => {
        if (apiError.response.data && apiError.response.data.validationErrors) {
            // add errors
            let errors = {
                ...this.state.errors,
                ...apiError.response.data.validationErrors
            };
            this.setState({
                [apiCall]: false,
                errors
            });
        }
    }

    /**
     * called when admin updates username
     */
    onClickUsernameUpdate = (e) => {
        e.preventDefault();

        this.setState({pendingApiCallUsernameUpdate: true});

        // send request to server to update username
        apiCalls.adminUpdateUsername(this.state.userId, {username: this.state.username}).then(response => {
            this.setState({pendingApiCallUsernameUpdate: false, usernameUpdated: true});
        }).catch(error => {
            // handle unauthorized state
            return handleError(error);
        }).catch(apiError => {
            // handle input errors
            this.handleApiError(apiError, "pendingApiCallUsernameUpdate");
        });
    }

    /**
     * called when admin wants to change user's password by sending it to mail
     */
    onPasswordChange = () => {
        const {username, email} = this.state;
        // ask before changing password
        if(window.confirm("Do you really want to generate a new password for user " + username + " and send it to this e-mail: " + email)) {
            this.setState({pendingApiCallChangePassword: true});

            // send request to change password by sending new password to user's mail
            apiCalls.adminChangePassword(this.state.userId).then(response => {
                this.setState({pendingApiCallChangePassword: false});
            }).catch(error => {
                // handle unauthorized state
                return handleError(error);
            });
        }
    }

    /**
     * called when admin wants to change user's rights to translate
     */
    onTranslatorChange = () => {
        const {userId, isTranslator} = this.state;
        // ask before changing rights
        if(window.confirm("Do you really want to change translation rights?")) {
            this.setState({pendingApiCallTranslator: true});

            // send request to server to change user's translation rights
            apiCalls.adminChangeTranslator(userId, {value: !isTranslator}).then(response => {
                this.setState({pendingApiCallTranslator: false, isTranslator: !isTranslator});
            }).catch(error => {
                // handle unauthorized state
                return handleError(error);
            });
        }
    }

    /**
     * called when admin wants to change user's ban
     */
    onBanChange = () => {
        const {userId, isBanned} = this.state;
        // ask before changing ban
        if(window.confirm("Do you really want to change access rights?")) {
            this.setState({pendingApiCallBan: true});

            // send request to change ban of user to server
            apiCalls.adminChangeBan(userId, {value: !isBanned}).then(response => {
                this.setState({pendingApiCallBan: false, isBanned: !isBanned});
            }).catch(error => {
                // handle unauthorized state
                return handleError(error);
            });
        }
    }

    /**
     * called when admin wants to remove institution from user
     */
    onInstitutionRemove = () => {
        // ask before removing institution
        const {userId, institutionName} = this.state;
        if(window.confirm("Do you really want to remove managerial rights? It may cause deletion of this institution: " + institutionName)) {
            this.setState({pendingApiCallRemoveInstitution: true});

            // send request to server to remove institution management to server
            apiCalls.adminRemoveInstitution(userId).then(response => {
                this.setState({pendingApiCallRemoveInstitution: false, isInstitutionOwner: false, institutionName: ""});
            }).catch(error => {
                return handleError(error);
            });
        }
    }

    /**
     * renders user details page
     * @returns {JSX.Element} page
     */
    render() {
        const {
            username,
            email,
            createdAt,
            isBanned,
            isTranslator,
            isInstitutionOwner,
            institutionName,
            pendingApiCall,
            pendingApiCallUsernameUpdate,
            pendingApiCallChangePassword,
            pendingApiCallTranslator,
            pendingApiCallBan,
            pendingApiCallRemoveInstitution,
            usernameUpdated,
            errors
        } = this.state;

        // define content
        let content = <Spinner/>;
        if (!pendingApiCall) {
            content =
                <div>
                    <div className="mb-4">
                        <span className="font-weight-bold">Registration date: </span>
                        {createdAt}
                    </div>

                    <form className="mb-4">
                        {
                            usernameUpdated &&
                            <div className="alert alert-success" role="alert">
                                Username updated
                            </div>
                        }

                        <div className="form-group">
                            <Input
                                label="Username"
                                placeholder="Enter name" name="username" value={username}
                                onChange={this.onChange}
                                hasError={errors.username && true}
                                error={errors.username}
                            />
                        </div>

                        <ButtonWithProgress  onClick={(e) => this.onClickUsernameUpdate(e)}
                                             className="btn btn-primary w-100 my-2"
                                             disabled={pendingApiCallUsernameUpdate || username === ""}
                                             pendingApiCall={pendingApiCallUsernameUpdate}
                                             hasChildren>
                            <i className="fa fa-paper-plane" /> Update username
                        </ButtonWithProgress>
                    </form>

                    <div className="card thick-top-border border-dark no-rounded thick-side-borders my-rounded-top no-bottom-border">
                        <div className="card-body">
                            <h5>Password Change</h5>
                            <p>Generate a new password and send it to an e-mail address: <span className="font-weight-bold">{email}</span></p>
                            <ButtonWithProgress  onClick={this.onPasswordChange}
                                                 className="btn btn-danger btn-lg my-1"
                                                 disabled={pendingApiCallChangePassword}
                                                 pendingApiCall={pendingApiCallChangePassword}
                                                 hasChildren>
                                <i className="fa fa-key"/> Generate new password
                            </ButtonWithProgress>
                        </div>
                    </div>

                    <div className="card thick-side-borders border-dark no-rounded no-bottom-border">
                        <div className="card-body">
                            <h5>Translation Rights</h5>
                            <p>Change user's right to translate</p>
                            <ButtonWithProgress  onClick={this.onTranslatorChange}
                                                 className={"btn btn-lg my-1 " + (isTranslator ? "btn-primary" : "btn-secondary")}
                                                 disabled={pendingApiCallTranslator}
                                                 pendingApiCall={pendingApiCallTranslator}
                                                 hasChildren>
                                <i className="fa fa-globe"/> {isTranslator ? "Translation rights on" : "Translation rights off"}
                            </ButtonWithProgress>
                        </div>
                    </div>

                    {
                        isInstitutionOwner &&
                        <div className="card thick-side-borders border-dark no-rounded no-bottom-border">
                            <div className="card-body">
                                <h5>Institution</h5>
                                <p>The user is a manager of: <span className="font-weight-bold">{institutionName}</span></p>
                                <ButtonWithProgress  onClick={this.onInstitutionRemove}
                                                     className="btn btn-lg my-1 btn-danger"
                                                     disabled={pendingApiCallRemoveInstitution}
                                                     pendingApiCall={pendingApiCallRemoveInstitution}
                                                     hasChildren>
                                    <i className="fa fa-times"/> Remove managerial rights
                                </ButtonWithProgress>
                            </div>
                        </div>
                    }

                    <div className="card thick-side-borders border-dark no-rounded thick-bottom-border my-rounded-bottom">
                        <div className="card-body">
                            <h5>Ban</h5>
                            <p>Change user's access to the system</p>
                            <ButtonWithProgress  onClick={this.onBanChange}
                                                 className={"btn btn-lg my-1 " + (isBanned ? "btn-danger" : "btn-primary")}
                                                 disabled={pendingApiCallBan}
                                                 pendingApiCall={pendingApiCallBan}
                                                 hasChildren>
                                <i className="fa fa-user"/> {isBanned ? "Banned" : "Access allowed"}
                            </ButtonWithProgress>
                        </div>
                    </div>
                </div>
        }

        // render page
        return (
            <PageContentContainer>
                <Breadcrumbs>
                    <BreadcrumbsLink to="/users" name="User Manager"/>
                    <li className="breadcrumb-item active">User Details</li>
                </Breadcrumbs>

                <PageInfo name="User Details">Here you can manage users and set their rights</PageInfo>
                {content}
            </PageContentContainer>
        );
    }
}

export default UserDetailPage;