Source: pages/UpdateInstitutionPage.js

import React, {Component} from 'react';
import * as authActions from "../store/authActions";
import {connect} from "react-redux";
import Input from "../components/Input";
import ButtonWithProgress from "../components/ButtonWithProgress";
import {INSTITUTIONS_IMAGES_URL} from "../shared/sharedConstants";
import * as apiCalls from "../apiCalls/apiCalls";
import handleError from "../shared/failureHandler";
import { Link } from "react-router-dom";
import PageContentContainer from "../components/PageContentContainer";
import Breadcrumbs from "../components/Breadcrumbs";
import PageInfo from "../components/PageInfo";

/**
 * page for updating institution
 */
class UpdateInstitutionPage extends Component {

    /**
     * current page state
     */
    state = {
        name: this.props.institution.name,
        address: this.props.institution.address,
        description: this.props.institution.description,
        latitudeString: this.props.institution.latitude,
        longitudeString: this.props.institution.longitude,
        image: this.props.institution.image,
        encodedImage: null,
        imageSelect: "",
        createdAt: this.props.institution.createdAt,
        email: "",
        pendingApiCallUpdateInstitution: false,
        pendingApiCallUpdateImage: false,
        pendingApiCallDeleteInstitution: false,
        pendingApiCallAddManager: false,
        institutionUpdated: false,
        imageUpdated: false,
        managerAdded: false,
        errors: {},
    }

    /**
     * called when new institution image is selected
     * @param event input event
     */
    onImageSelect = (event) => {
        // delete errors and update value in state
        const errors = {...this.state.errors};
        delete errors["encodedImage"];
        this.setState({errors, [event.target.name]: event.target.value, imageUpdated: false});
        if (event.target.files.length === 0) {
            return;
        }

        // read selected image if exists
        const file = event.target.files[0];
        let reader = new FileReader();
        reader.onloadend = () => {
            // set base64 encoded image to tate
            this.setState({encodedImage: reader.result});
        }
        reader.readAsDataURL(file);
    }

    /**
     * clear image from state
     */
    clearImage = () => {
        // delete errors and clear image fields from state
        const errors = {...this.state.errors};
        delete errors["encodedImage"];
        this.setState({errors, encodedImage: null, imageSelect: "",});
    }

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

    /**
     * called when user submit institution image update
     */
    onClickImageUpdate = (e) => {
        e.preventDefault();

        this.setState({pendingApiCallUpdateImage: true});
        const img = { encodedImage: this.state.encodedImage }

        // send request to update image to server
        apiCalls.updateInstitutionImage(img).then(response => {
            this.setState({pendingApiCallUpdateImage: false, image: response.data.message, imageUpdated: true}, () => {
                this.clearImage();
            });
        }).catch(error => {
            // handle unauthorized state
            return handleError(error);
        }).catch(apiError => {
            // handle errors in input
            this.handleApiError(apiError, "pendingApiCallUpdateImage");
        });
    }

    /**
     * called when value in text inputs is changed
     * @param event input event
     */
    onChange = event => {
        // delete errors and update value in state
        const errors = {...this.state.errors};
        delete errors[event.target.name];
        this.setState({errors, institutionUpdated: false, [event.target.name]: event.target.value});
    }

    /**
     * called when value of email text box is changed
     * @param event input event
     */
    onEmailChange = event => {
        // delete errors and set value to state
        const errors = {...this.state.errors};
        delete errors[event.target.name];
        this.setState({errors, managerAdded: false, [event.target.name]: event.target.value});
    }

    /**
     * called when institution manager wants to update info about institution
     */
    onClickInstitutionUpdate = (e) => {
        e.preventDefault();

        this.setState({pendingApiCallUpdateInstitution: true});
        // extract institution from state
        const institution = {
            name: this.state.name,
            address: this.state.address,
            description: this.state.description,
            latitudeString: this.state.latitudeString,
            longitudeString: this.state.longitudeString,
        }

        // sends request to server to update institution information
        apiCalls.updateInstitution(institution).then(response => {
            this.setState({pendingApiCallUpdateInstitution: false, institutionUpdated: true});
        }).catch(error => {
            // handle unauthorized state
            return handleError(error);
        }).catch(apiError => {
            // handle input error
            this.handleApiError(apiError, "pendingApiCallUpdateInstitution");
        });
    }

    /**
     * called when manager of institution wants to delete it
     */
    onClickInstitutionDelete = () => {
        // ask before institution delete
        if (window.confirm("Do you really want to delete your institution?")) {
            this.setState({pendingApiCallDeleteInstitution: true});

            // send request to institution delete to server
            apiCalls.deleteInstitution().then(response => {
                this.setState({pendingApiCallDeleteInstitution: false}, () => {
                    this.props.setIsInstitutionOwner(false);
                    this.props.redirect("/");
                });
            }).catch(error => {
                // handle unauthorized state
                return handleError(error);
            });
        }
    }

    /**
     * called when institution manager wants to add new manager
     */
    onClickManagerAdd = (e) => {
        e.preventDefault();

        // ask before adding manager
        if (window.confirm("Do you really want to add a new manager to your institution?")) {
            this.setState({pendingApiCallAddManager: true});

            // send request to server to add new manager to institution
            apiCalls.addInstitutionManager({email: this.state.email}).then(response => {
                this.setState({pendingApiCallAddManager: false, email: ""}, () => this.setState({managerAdded: true}));
            }).catch(error => {
                // handle unauthorized state
                return handleError(error);
            }).catch(apiError => {
                // handle input errors
                this.handleApiError(apiError, "pendingApiCallAddManager");
            });
        }
    }

    /**
     * renders institution update page
     * @returns {JSX.Element} page
     */
    render() {
        const {
            name,
            address,
            description,
            latitudeString,
            longitudeString,
            image,
            encodedImage,
            imageSelect,
            email,
            pendingApiCallUpdateInstitution,
            pendingApiCallUpdateImage,
            pendingApiCallDeleteInstitution,
            pendingApiCallAddManager,
            institutionUpdated,
            imageUpdated,
            managerAdded,
            errors,
        } = this.state;

        // render page
        return (
            <PageContentContainer>
                <Breadcrumbs>
                    <li className="breadcrumb-item active">My Institution</li>
                </Breadcrumbs>

                <PageInfo name="My Institution">Here you can manage your institution</PageInfo>

                <div className="row d-flex justify-content-between">
                    <div className="col-lg col-sm-6 col-md-6 text-center">
                        <Link exact to="/myInstitution/addLanguages" >
                            <button type="button" className="btn btn-lg btn-primary mb-3 institution-nav">
                                <div className="card-body">
                                    <h5 className="card-title">Languages</h5>
                                    <p className="card-text">Add languages that translators will be able to translate</p>
                                </div>
                            </button>
                        </Link>
                    </div>

                    <div className="col-lg col-sm-6 col-md-6 text-center">
                        <Link exact to="/myInstitution/addExhibit" >
                            <button type="button" className="btn btn-lg btn-success mb-3 institution-nav">
                                <div className="card-body">
                                    <h5 className="card-title">New Exhibits</h5>
                                    <p className="card-text">Add new exhibits with their information labels to your institution</p>
                                </div>
                            </button>
                        </Link>
                    </div>

                    <div className="col-lg col-sm-6 col-md-6 text-center">
                        <Link exact to="/myInstitution/exhibits" >
                            <button type="button" className="btn btn-lg btn-info mb-3 institution-nav">
                                <div className="card-body">
                                    <h5 className="card-title">View Exhibits</h5>
                                    <p className="card-text">Get QR codes and manage your institution's exhibits</p>
                                </div>
                            </button>
                        </Link>
                    </div>

                    <div className="col-lg col-sm-6 col-md-6 text-center">
                        <Link exact to="/myInstitution/buildings" >
                            <button type="button" className="btn btn-lg btn-secondary mb-3 institution-nav">
                                <div className="card-body">
                                    <h5 className="card-title">Navigation</h5>
                                    <p className="card-text">Add and edit buildings, rooms and showcases for visitor navigation</p>
                                </div>
                            </button>
                        </Link>
                    </div>
                </div>

                <form className="mb-4 mt-4">
                    {
                        imageUpdated &&
                        <div className="alert alert-success" role="alert">
                            Institution image updated
                        </div>
                    }

                    <div className="form-group">
                        <Input type="file"
                               onlyImage
                               value={imageSelect}
                               name="imageSelect"
                               label="Institution image"
                               placeholder="Select institution image"
                               onChange={this.onImageSelect}
                               hasError={errors.encodedImage && true}
                               error={errors.encodedImage}
                        />

                        {
                            encodedImage ?
                                <div>
                                    <img className="img-fluid sizedImg img-thumbnail mt-2" src={encodedImage} alt="upload" />
                                    <br />
                                    <button className="btn btn-danger btn-lg mt-2" disabled={pendingApiCallUpdateImage} onClick={this.clearImage}>
                                        <i className="fa fa-times" /> Clear
                                    </button>
                                    <ButtonWithProgress onClick={(e) => this.onClickImageUpdate(e)}
                                                        className="btn btn-success btn-lg ml-2 mt-2"
                                                        disabled={pendingApiCallUpdateImage}
                                                        pendingApiCall={pendingApiCallUpdateImage}
                                                        hasChildren>
                                        <i className="fa fa-paper-plane" /> Update image
                                    </ButtonWithProgress>
                                </div>
                                :
                                <div>
                                    <img className="img-fluid sizedImg img-thumbnail mt-2" src={INSTITUTIONS_IMAGES_URL + image} alt="upload" />
                                </div>
                        }
                    </div>
                </form>

                <form>
                    {
                        institutionUpdated &&
                        <div className="alert alert-success" role="alert">
                            Institution information updated
                        </div>
                    }

                    <div className="form-group">
                        <Input
                            label="Name"
                            placeholder="Enter name" name="name" value={name}
                            onChange={this.onChange}
                            hasError={errors.name && true}
                            error={errors.name}
                        />
                    </div>
                    <div className="form-group">
                        <Input
                            label="Address"
                            placeholder="Enter address" name="address" value={address}
                            onChange={this.onChange}
                            hasError={errors.address && true}
                            error={errors.address}
                        />
                    </div>
                    <div className="form-group">
                        <Input
                            label="Description" type="textarea"
                            placeholder="Enter description" name="description" value={description}
                            onChange={this.onChange}
                            hasError={errors.description && true}
                            error={errors.description}
                        />
                    </div>
                    <div className="form-group">
                        <Input
                            label="Latitude"
                            placeholder="Enter latitude" name="latitudeString" value={latitudeString}
                            onChange={this.onChange}
                            hasError={errors.latitudeString && true}
                            error={errors.latitudeString}
                        />
                    </div>
                    <div className="form-group">
                        <Input
                            label="Longitude"
                            placeholder="Enter longitude" name="longitudeString" value={longitudeString}
                            onChange={this.onChange}
                            hasError={errors.longitudeString && true}
                            error={errors.longitudeString}
                        />
                    </div>

                    <ButtonWithProgress  onClick={(e) => this.onClickInstitutionUpdate(e)}
                                         className="btn btn-primary w-100 my-2"
                                         disabled={pendingApiCallUpdateInstitution || name === "" || address === "" || description === "" ||
                                             latitudeString === "" || longitudeString === ""}
                                         pendingApiCall={pendingApiCallUpdateInstitution}
                                         hasChildren>
                        <i className="fa fa-paper-plane" /> Update institution information
                    </ButtonWithProgress>

                </form>

                <form className="mt-4">
                    {
                        managerAdded &&
                        <div className="alert alert-success" role="alert">
                            New manager added
                        </div>
                    }

                    <Input
                        label="New institution manager"
                        placeholder="Enter new manager's e-mail" name="email" value={email}
                        onChange={this.onEmailChange}
                        hasError={errors.email && true}
                        error={errors.email}
                    />

                    <ButtonWithProgress  onClick={(e) => this.onClickManagerAdd(e)}
                                         className="btn btn-primary w-100 my-2"
                                         disabled={pendingApiCallAddManager || email === ""}
                                         pendingApiCall={pendingApiCallAddManager}
                                         hasChildren>
                        <i className="fa fa-paper-plane" /> Add new institution manager
                    </ButtonWithProgress>
                </form>

                <br />
                <ButtonWithProgress  onClick={this.onClickInstitutionDelete}
                                     className="btn btn-lg mt-3 btn-danger"
                                     disabled={pendingApiCallDeleteInstitution}
                                     pendingApiCall={pendingApiCallDeleteInstitution}
                                     hasChildren>
                    <i className="fa fa-times" /> Delete institution
                </ButtonWithProgress>
            </PageContentContainer>
        );
    }
}

/**
 * maps redux dispatch to state props
 * @param dispatch redux dispatch
 */
const mapDispatchToProps = (dispatch) => {
    return {
        setIsInstitutionOwner: (value) => dispatch(authActions.setIsInstitutionOwner(value)),
    }
}

export default connect(null, mapDispatchToProps)(UpdateInstitutionPage);