view client/src/components/usermanagement/Userdetail.vue @ 5095:e21cbb9768a2

Prevent duplicate fairway areas In principal, there can be only one or no fairway area at each point on the map. Since polygons from real data will often be topologically inexact, just disallow equal geometries. This will also help to avoid importing duplicates with concurrent imports, once the history of fairway dimensions will be preserved.
author Tom Gottfried <tom@intevation.de>
date Wed, 25 Mar 2020 18:10:02 +0100
parents 6b054b91d9b2
children 45b03e8ca47e
line wrap: on
line source

<template>
  <div class="userdetails shadow-xs fadeIn animated card">
    <UIBoxHeader
      icon="user"
      :title="this.cardHeader"
      :closeCallback="closeDetailview"
    />
    <div class="card-body">
      <form @submit.prevent="save" class="ml-3">
        <div class="formfields">
          <div v-if="currentUser.isNew" class="form-group row">
            <label for="user"> <translate>Username</translate> </label>
            <input
              type="user"
              :placeholder="userNamePlaceholder"
              class="form-control form-control-sm"
              id="user"
              aria-describedby="userHelp"
              v-model="currentUser.user"
            />
            <div v-show="errors.user" class="text-danger">
              <small>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.user }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <label for="country"> <translate>Country</translate> </label>
            <select
              class="form-control form-control-sm"
              v-on:change="validateCountry"
              v-model="currentUser.country"
            >
              <option disabled value>
                <translate>Please select one</translate>
              </option>
              <option
                v-for="country in countries"
                v-bind:value="country"
                v-bind:key="country"
                >{{ country }}</option
              >
            </select>
            <div v-show="errors.country" class="text-danger">
              <small>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.country }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <label for="email"> <translate>Email address</translate> </label>
            <input
              type="email"
              v-on:change="validateEmailaddress"
              class="form-control form-control-sm"
              id="email"
              aria-describedby="emailHelp"
              v-model="currentUser.email"
            />
            <div v-show="errors.email" class="text-danger">
              <small>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.email }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <label for="role"> <translate>Role</translate> </label>
            <select
              class="form-control form-control-sm"
              v-on:change="validateRole"
              v-model="currentUser.role"
            >
              <option disabled value>
                <translate>Please select one</translate>
              </option>
              <option value="sys_admin">
                <translate>Sysadmin</translate>
              </option>
              <option value="waterway_admin">
                <translate>Waterway Admin</translate>
              </option>
              <option value="waterway_user">
                <translate>Waterway User</translate>
              </option>
            </select>
            <div v-show="errors.role" class="text-danger">
              <small>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.role }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <PasswordField
              @fieldchange="passwordChanged"
              :placeholder="passwordPlaceholder"
              :label="passwordLabel"
              :passworderrors="errors.password"
            />
          </div>
          <div class="form-group row">
            <PasswordField
              @fieldchange="passwordReChanged"
              :placeholder="passwordRePlaceholder"
              :label="passwordReLabel"
              :passworderrors="errors.passwordre"
            />
          </div>
        </div>
        <div>
          <button
            type="submit"
            :disabled="submitted"
            class="shadow-sm btn btn-info submit-button"
          >
            <translate>Save</translate>
          </button>
        </div>
      </form>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.submit-button {
  position: absolute;
  right: $offset;
  bottom: $offset;
}
.mailbutton {
  width: 12vw;
  position: absolute;
  left: $large-offset;
  bottom: 0;
}

.formfields {
  width: 60%;
}

.userdetails {
  max-height: 693px;
  margin-right: $offset;
}

form {
  font-size: $smaller;
}
</style>

<script>
/* This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 *
 * Copyright (C) 2018 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * Thomas Junk <thomas.junk@intevation.de>
 */
import { displayError } from "@/lib/errors";
import { mapState } from "vuex";

const emptyErrormessages = () => {
  return {
    email: "",
    country: "",
    role: "",
    password: "",
    passwordre: ""
  };
};

const isEmailValid = email => {
  /**
   *
   * For convenience purposes the same regex used as in the go code
   * cf. types.go
   *
   */
  // eslint-disable-next-line
  return /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(
    email
  );
};

const violatedPasswordRules = password => {
  return (
    // rules according to issue 70
    password.length < 7 ||
    /\W/.test(password) == false ||
    /\d/.test(password) == false
  );
};

export default {
  name: "userdetail",
  components: {
    PasswordField: () => import("./Passwordfield")
  },
  data() {
    return {
      passwordLabel: this.$gettext("Password"),
      passwordReLabel: this.$gettext("Repeat Password"),
      passwordPlaceholder: this.$gettext("password"),
      passwordRePlaceholder: this.$gettext("password again"),
      password: "",
      passwordre: "",
      currentUser: {},
      path: null,
      submitted: false,
      errors: {
        email: "",
        country: "",
        role: "",
        password: "",
        passwordre: ""
      }
    };
  },
  mounted() {
    this.currentUser = { ...this.user };
    this.path = this.user.name;
  },
  watch: {
    user() {
      this.currentUser = { ...this.user };
      this.path = this.user.name;
      this.clearPassword();
      this.clearErrors();
    }
  },
  computed: {
    cardHeader() {
      if (this.currentUser.isNew) return this.$gettext("Add User");
      return this.currentUser.user;
    },
    userNamePlaceholder() {
      if (this.currentUser.isNew) return this.$gettext("Username");
      return "";
    },
    ...mapState("application", ["countries"]),
    user() {
      return this.$store.getters["usermanagement/currentUser"];
    },
    isFormValid() {
      return (
        isEmailValid(this.currentUser.email) &&
        this.currentUser.country &&
        this.password === this.passwordre &&
        (this.password === "" || !violatedPasswordRules(this.password))
      );
    }
  },
  methods: {
    passwordChanged(value) {
      this.password = value;
      this.validatePassword();
    },
    passwordReChanged(value) {
      this.passwordre = value;
      this.validatePassword();
    },
    clearErrors() {
      this.errors = emptyErrormessages();
    },
    clearPassword() {
      this.password = "";
      this.passwordre = "";
    },
    closeDetailview() {
      this.$store.commit("usermanagement/clearCurrentUser");
      this.$store.commit("usermanagement/setUserDetailsInvisible");
    },
    validateCountry() {
      this.errors.country = this.currentUser.country
        ? ""
        : this.$gettext("Please choose a country");
    },
    validateRole() {
      this.errors.role = this.currentUser.role
        ? ""
        : this.$gettext("Please choose a role");
    },
    validatePassword() {
      this.errors.passwordre =
        this.password === this.passwordre
          ? ""
          : this.$gettext("Passwords do not match!");
      this.errors.password =
        this.password === "" || !violatedPasswordRules(this.password)
          ? ""
          : this.$gettext(
              "Password should at least be 8 char long including 1 digit and 1 special char like $"
            );
    },
    validateEmailaddress() {
      this.errors.email = isEmailValid(this.currentUser.email)
        ? ""
        : this.$gettext("invalid email");
    },
    validate() {
      this.validateCountry();
      this.validateRole();
      this.validatePassword();
      this.validateEmailaddress();
    },
    save() {
      this.validate();
      if (!this.isFormValid) return;
      if (this.password) this.currentUser.password = this.password;
      this.submitted = true;
      this.$store
        .dispatch("usermanagement/saveCurrentUser", {
          path: this.user.user,
          user: this.currentUser
        })
        .then(() => {
          this.submitted = false;
          this.$store.dispatch("usermanagement/loadUsers").catch(error => {
            let message = "Backend not reachable";
            if (error.response) {
              const { status, data } = error.response;
              message = `${status}: ${data.message || data}`;
            }
            displayError({
              title: this.$gettext("Backend Error"),
              message: message
            });
          });
        })
        .catch(error => {
          this.submitted = false;
          let message = "Backend not reachable";
          if (error.response) {
            const { status, data } = error.response;
            message = `${status}: ${data.message || data}`;
          }
          displayError({
            title: this.$gettext("Backend Error"),
            message: message
          });
        });
    }
  }
};
</script>