Source: components/contributepage/ProfileForm.js

import React, { Component } from 'react'
import './UserProfile.css'
import { Link, Redirect } from 'react-router-dom'
import 'leaflet/dist/leaflet.css';
import EmailValidator from 'email-validator'
import { Map, Marker, TileLayer } from 'react-leaflet';
import getTranslation from '../../i18n/'
import Cookies from 'universal-cookie';
import 'react-select/dist/react-select.css';
import marker from 'leaflet/dist/images/marker-icon.png';
import markershadow from 'leaflet/dist/images/marker-shadow.png';
import leaflet from 'leaflet';
import Select from 'react-select';

const cookies = new Cookies();

/**
 * The form to update a userprofile
 */
class ProfileForm extends Component {

  constructor(props) {
    super(props);
    
    let lat, lon;
        if(!props.lat) {
        lat = 48.191;
        lon = 15.989;
    } else {
        lat = parseFloat(props.lat);
        lon = parseFloat(props.lon);
    }
        this.state = {
            lat: lat,
            lon: lon,
            zoom: 13,
            redirect: false,
            errorMessages: [],
            errors: {
                map: false, name: false, occupation: false, street: false, zip: false, city: false, country: false, phone: false, email: false, website: false, pilotArea: false, profile: false
            },
            userprofile: {
                name: '',
                occupation: [0, 0, 0],
                street: '',
                zip: '',
                city: '',
                country: 1,
                phone: '',
                email: '',
                website: '',
                pilot_area: 0,
                profile: '',
                contactform: false,
                activated: false,
            },
            required: [
                "name", "street", "zip", "city", "country", "pilot_area", "occupation", "email"
            ]
        }

  }

  /**
   * Update the field state on change
   * @param  {} event
   */
  updateField(event) {
      let userprofile = this.state.userprofile;
      const target = event.target;
      const name = target.name;
      let value = target.type === 'checkbox' ? target.checked : target.value;

      let errors = this.state.errors;
      
      if(this.state.required.indexOf(name) !== -1) {
          if(value === '') {
              errors[name] = true;
          } else {
              errors[name] = false;
          }
      }

      if(name === 'email') {
          if(!EmailValidator.validate(value)) {
              errors[name] = true;
          } else {
              errors[name] = false;
          }
      }

      if(name === 'profile') {
          let words = value.split(" ");
          if(words.length > 151) {
              errors[name] = true;
              value = this.state.userprofile.profile
          } else {
              errors[name] = false;
          }
      }
      userprofile[name] = value;
      this.setState({ userprofile: userprofile, errors: errors });   
  }

  /**
   * Submit the profile by dispatching the according action with the form state + JWT token
   */
  submitProfile() {
      let errorMessages = [];
      let errors = this.state.errors;
      let redirect = false;
      let required = [];
     
      this.state.required.map(field => {
          if(this.state.userprofile[field] === '') {
              required.push(field);
              errors[field] = true;
          } else if ((field === 'occupation') && (this.state.userprofile.occupation.filter(group => group === 0).length > 2)) {
              required.push(field);
               errors[field] = true;
          }
          else if ((field === 'country') && (this.state.userprofile.country === 0)) {
              required.push(field);
               errors[field] = true;
          }
      })
      if(required.length > 0) {
          errorMessages.push("forms.general.error.message.fields_required");
      }

      if(!EmailValidator.validate(this.state.userprofile.email)) {
          errorMessages.push('forms.general.error.message.valid_email');
          errors.email = true;
      }

      if(errorMessages.length === 0) {
          let website = this.state.userprofile.website;
          if(this.state.userprofile.website.substring(0,7) !== 'http://') {
              if(this.state.userprofile.website.substring(0,8) !== 'https://') {
                  if(this.state.userprofile.website !== '') {
                      website = 'http://'+website;
                  }
              }
          }
          let token = cookies.get('token');
          this.props.updateUserprofile(this.state.userprofile.name, this.state.userprofile.occupation, this.state.userprofile.street, this.state.userprofile.zip, this.state.userprofile.city, this.state.userprofile.country, this.state.userprofile.phone, this.state.userprofile.email, website, this.state.userprofile.pilot_area, this.state.lat, this.state.lon, this.state.userprofile.contactform, this.state.userprofile.activated, this.state.userprofile.profile, token)
          redirect = true;
      }
      this.setState({errorMessages: errorMessages, redirect: redirect, errors: errors});
  }

  /**
   * Geocode the entered address
   */
  geocode() {
      if((this.state.userprofile.street === '') || (this.state.userprofile.zip === '') || (this.state.userprofile.city === '')) {
          let errors = this.state.errors;
          errors.map = true;
          this.setState({errors: errors})
      } else {
          this.props.geocodeAddress(this.state.userprofile.street, this.state.userprofile.zip, this.state.userprofile.city)
          this.setState({mapOverlay: false})
      }
  }

  /**
   * Change the coordinates based on either a dragged marker or a geocoded address
   * @param {*} event 
   */
  updateCoordinates(event) {
    const zoom = this.refs.map.leafletElement.getZoom()
    let userprofile = this.state.userprofile;
    userprofile.lon = event.target.getLatLng().lng;
    userprofile.lat = event.target.getLatLng().lat;
    this.setState({zoom, userprofile, lat: userprofile.lat, lon: userprofile.lon})
  }

  /**
   * Change the professional group (occupation) of a user
   * @param {} value 
   * @param {*} index 
   */
  changeProfessionalGroup(value, index) {
      let already_selected = this.state.userprofile.occupation.filter(pgroup => pgroup === value.value);
      if(already_selected.length === 0) {
          let userprofile = this.state.userprofile;
          userprofile.occupation[index] = value.value;
          this.setState({userprofile: userprofile})
      }
  }

  /**
   * Change the pilot area of a user
   * @param {} value 
   */
  changePilotArea(value) {
      let userprofile = this.state.userprofile;
      userprofile.pilot_area = value.value;
      this.setState({userprofile: userprofile})
  }

  /**
   * Change the country of a user
   * @param {} value 
   */
  changeCountry(value) {
      let userprofile = this.state.userprofile;
      userprofile.country = value.value;
      this.setState({userprofile: userprofile})
  }

  componentWillReceiveProps(nextProps) {

      let lat, lon;
      if((nextProps.fetching.dataFetching.data === 'geocode') && (nextProps.fetching.dataFetching.status) && (!nextProps.fetching.dataFetching.isFetching)) {
          lat = nextProps.fetching.dataFetching.coordinates[0];
          lon = nextProps.fetching.dataFetching.coordinates[1];
          this.setState({lat: parseFloat(lat), lon: parseFloat(lon)})
      } else if(nextProps.fetching.dataFetching.data !== 'geocode') {
          let userprofile = nextProps.userprofile;
          if(userprofile.lat === undefined) {
              lat = this.state.lat;
              lon = this.state.lon;
          } else {
              lat = userprofile.lat;
              lon = userprofile.lon;
          }
          this.setState({userprofile: userprofile, lat: lat, lon: lon})
      }


  }

  componentDidMount() {
      this.setState({
       userprofile: this.props.userprofile
   })
  }

  render() {
      
      let occupations = [];

      this.props.professionalgroups.map(occupation =>
          occupations.push({
              value: occupation.id,
              label: getTranslation(occupation.name)
          })
      )
      occupations.sort(function(a, b) {
      var textA = a.label.toUpperCase();
      var textB = b.label.toUpperCase();
      return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
      });
      occupations.unshift({ value: 0, label: getTranslation('userprofile.form.group.placeholder')})


      let pilotareas = [];

      this.props.pilotareas.map(area =>
          pilotareas.push({
              value: area.id,
              label: getTranslation(area.name)
          })
      )

      pilotareas.sort(function(a, b) {
      var textA = a.label.toUpperCase();
      var textB = b.label.toUpperCase();
      return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
      });
      pilotareas.unshift({value: 0, label: getTranslation('userprofile.form.pilot_area.not_in_pilotarea')})

      let countries = [];
      this.props.countries.map(country =>
          countries.push({
              value: country.id,
              label: getTranslation(country.label)
          })
      )

      countries.sort(function(a, b) {
      var textA = a.label.toUpperCase();
      var textB = b.label.toUpperCase();
      return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
      });
      countries.unshift({value: 0, label: getTranslation('select.country')})

      let position = [parseFloat(this.state.lat), parseFloat(this.state.lon)];
      if((this.props.fetching.dataFetching.data === 'geocode') && (this.props.fetching.dataFetching.status === true) && (this.props.fetching.dataFetching.isFetching === false)) {
        position = this.props.fetching.dataFetching.coordinates;
      }

      let icon = leaflet.icon({
        iconUrl: marker,
        shadowUrl: markershadow,
        iconSize: [24,36],
        iconAnchor: [12,36]
      });

      let words = this.state.userprofile.profile.split(" ").length-1;

    return(
        <div className="user-profile container-flex form-container">
        {this.state.redirect &&
            <Redirect to="/experts/contribute/overview" />
        }
          <div className="half">
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.name.label')}:{this.state.errors.name && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
              <input className={(this.state.errors.name ? 'v-error' : 'v-check')} name="name" type="text" value={this.state.userprofile.name} onChange={(event) => this.updateField(event)} placeholder={getTranslation('userprofile.form.name.placeholder')} />
            </div>
            <div className="user-profile-item profile-form-select">
                <label className="centered">{getTranslation('userprofile.form.group.label')}:{this.state.errors.occupation && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
                {this.state.userprofile.occupation.map((group, index) =>
                    <Select key={index}
                        name={"occupation-"+index}
                        value={group}
                        options={occupations}
                        clearable={false}
                        onChange={(value) => this.changeProfessionalGroup(value, index)}
                      />
                )}

            </div>
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.address.label')}:{this.state.errors.street && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
              <input className={(this.state.errors.street ? 'v-error' : 'v-check')} name="street" type="text" value={this.state.userprofile.street} onChange={(event) => this.updateField(event)}/>
            </div>
            <div className="user-profile-item container-flex">
              <div className="half">
                <label className="centered">{getTranslation('userprofile.form.zip.label')}:{this.state.errors.zip && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
                <input className={(this.state.errors.zip ? 'v-error' : 'v-check')} name="zip" type="text" value={this.state.userprofile.zip} onChange={(event) => this.updateField(event)}/>
              </div>
              <div className="half">
                <label className="centered">{getTranslation('userprofile.form.city.label')}:{this.state.errors.city && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
                <input onBlur={() => this.geocode()} className={(this.state.errors.city ? 'v-error' : 'v-check')} name="city" type="text" value={this.state.userprofile.city} onChange={(event) => this.updateField(event)}/>
              </div>
            </div>
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.country.label')}:{this.state.errors.country && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
              <Select
                  name="country"
                  value={Number(this.state.userprofile.country)}
                  options={countries}
                  clearable={false}
                  onChange={(value) => this.changeCountry(value)}
                />
            </div>
            <div className="user-profile-item profile-form-select">
                <label className="centered">{getTranslation('userprofile.form.pilot_area.label')}:{this.state.errors.pilot_area && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
                <Select
                    name="pilot_area"
                    value={Number(this.state.userprofile.pilot_area)}
                    options={pilotareas}
                    clearable={false}
                    onChange={(value) => this.changePilotArea(value)}
                  />
            </div>
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.phone.label')}:</label>
              <input name="phone" type="text" value={this.state.userprofile.phone} onChange={(event) => this.updateField(event)}/>
            </div>
            <div className="user-profile-item">
              <label className="centered">{getTranslation('forms.general.email.label')}:{this.state.errors.email && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
              <input className={(this.state.errors.email ? 'v-error' : 'v-check')} name="email" type="email" value={this.state.userprofile.email} onChange={(event) => this.updateField(event)}/>
            </div>
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.website.label')}:</label>
              <input name="website" type="text" value={this.state.userprofile.website} onChange={(event) => this.updateField(event)}/>
            </div>

            <div className="user-profile-item">
              <input name="contactform" id="contactform" type="checkbox" checked={this.state.userprofile.contactform} onChange={(event) => this.updateField(event)} />
              <label htmlFor="contactform" className="checkbox-label">{getTranslation('userprofile.form.contactform.label')}</label>
            </div>

            <div className="user-profile-item">
              <input name="activated" id="activated" type="checkbox" checked={this.state.userprofile.activated} onChange={(event) => this.updateField(event)} />
              <label htmlFor="activated" className="checkbox-label">{getTranslation('userprofile.form.publish.label')}</label>
            </div>

            <div className="user-profile-item">
            {this.state.errorMessages.length > 0 &&
                <div className="error-messages">
                    {this.state.errorMessages.map((error, index) =>
                        <p key={index}>{getTranslation(error)}</p>
                    )}
                </div>
            }
            <button className="btn btn-green btn-icon btn-save" onClick={() => this.submitProfile()}>{getTranslation('userprofile.form.button.save')}</button>
            <Link to="/experts/contribute/overview"><button className="btn btn-gray btn-icon btn-cancel">{getTranslation("forms.general.button.cancel")}</button></Link>
            </div>
          </div>
          <div className="half">
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.map.label')}:</label>
              <div className="mapbox">
              <button className="btn btn-green btn-icon btn-geocode" onClick={() => this.geocode()}>{getTranslation('userprofile.form.button.geocode')}</button>
              <Map ref="map" center={position} zoom={this.state.zoom}>
                <TileLayer
                  url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
                  attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                />
                <Marker icon={icon} draggable={true} onDragEnd={(event) => this.updateCoordinates(event)} position={position}></Marker>
              </Map>
              </div>
              <div className="mapbox-errors">
                {this.state.errors.map && <span className="validation-error">{getTranslation('userprofile.form.error.map')}</span>}
              </div>
            </div>
            <div className="user-profile-item">
              <label className="centered">{getTranslation('userprofile.form.profile.label')}:{this.state.errors.profile && <span className="validation-error">{getTranslation('forms.general.error.hints.field_required')}</span>}</label>
              <textarea className={(this.state.errors.profile ? 'v-error' : 'v-check')} name="profile" value={this.state.userprofile.profile} rows="15" onChange={(event) => this.updateField(event)}></textarea>
              <label>{words}{getTranslation("userprofile.form.profile.counter")}</label>
            </div>

          </div>
        </div>
    )
  }
}

export default ProfileForm