This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git


The following commit(s) were added to refs/heads/AIRAVATA-3562 by this push:
     new e02b4256 AIRAVATA-3565 Switch user profile editor to vuex store
e02b4256 is described below

commit e02b42562e91ccfb0b16aff4789b0880c43e4d7d
Author: Marcus Christie <[email protected]>
AuthorDate: Tue Apr 5 15:24:30 2022 -0400

    AIRAVATA-3565 Switch user profile editor to vuex store
---
 django_airavata/apps/auth/package.json             |   3 +-
 .../js/components/UserProfileEditor.vue            | 105 ++++++++++++---------
 .../js/containers/UserProfileContainer.vue         |  97 +++++++++----------
 .../django_airavata_auth/js/entry-user-profile.js  |   5 +-
 .../static/django_airavata_auth/js/store/index.js  |  16 ++++
 .../js/store/modules/extendedUserProfile.js        |   0
 .../js/store/modules/userProfile.js                |  64 +++++++++++++
 django_airavata/apps/auth/yarn.lock                |   5 +
 8 files changed, 196 insertions(+), 99 deletions(-)

diff --git a/django_airavata/apps/auth/package.json 
b/django_airavata/apps/auth/package.json
index a136c923..bcc0445e 100644
--- a/django_airavata/apps/auth/package.json
+++ b/django_airavata/apps/auth/package.json
@@ -17,7 +17,8 @@
     "django-airavata-api": "link:../api/",
     "django-airavata-common-ui": "link:../../static/common/",
     "vue": "^2.5.21",
-    "vuelidate": "^0.7.6"
+    "vuelidate": "^0.7.6",
+    "vuex": "^3.6.2"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^3.1.1",
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
 
b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index 669d356b..8816b93e 100644
--- 
a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -1,30 +1,34 @@
 <template>
-  <b-card>
-    <b-form-group label="Username" :disabled="true" description="Only 
administrators can update a username.">
+  <b-card v-if="user">
+    <b-form-group
+      label="Username"
+      :disabled="true"
+      description="Only administrators can update a username."
+    >
       <b-form-input v-model="user.username" />
     </b-form-group>
     <b-form-group label="First Name" :disabled="disabled">
       <b-form-input
-        v-model="$v.user.first_name.$model"
+        v-model="$v.first_name.$model"
         @keydown.native.enter="save"
-        :state="validateState($v.user.first_name)"
+        :state="validateState($v.first_name)"
       />
     </b-form-group>
     <b-form-group label="Last Name" :disabled="disabled">
       <b-form-input
-        v-model="$v.user.last_name.$model"
+        v-model="$v.last_name.$model"
         @keydown.native.enter="save"
-        :state="validateState($v.user.last_name)"
+        :state="validateState($v.last_name)"
       />
     </b-form-group>
     <b-form-group label="Email" :disabled="disabled">
       <b-form-input
-        v-model="$v.user.email.$model"
+        v-model="$v.email.$model"
         @keydown.native.enter="save"
-        :state="validateState($v.user.email)"
+        :state="validateState($v.email)"
       />
-      <b-form-invalid-feedback v-if="!$v.user.email.email">
-        {{ user.email }} is not a valid email address.
+      <b-form-invalid-feedback v-if="!$v.email.email">
+        {{ email }} is not a valid email address.
       </b-form-invalid-feedback>
       <b-alert class="mt-1" show v-if="user.pending_email_change"
         >Once you verify your email address at
@@ -36,26 +40,26 @@
         ></b-alert
       >
     </b-form-group>
-    <b-button variant="primary" @click="save" :disabled="$v.$invalid || 
disabled"
+    <!-- TODO: move save button up to container -->
+    <b-button
+      variant="primary"
+      @click="save"
+      :disabled="$v.$invalid || disabled"
       >Save</b-button
     >
   </b-card>
 </template>
 
 <script>
-import { models } from "django-airavata-api";
 import { errors } from "django-airavata-common-ui";
 import { validationMixin } from "vuelidate";
 import { email, required } from "vuelidate/lib/validators";
+import { mapGetters, mapMutations } from "vuex";
 
 export default {
   name: "user-profile-editor",
   mixins: [validationMixin],
   props: {
-    value: {
-      type: models.User,
-      required: true,
-    },
     disabled: {
       type: Boolean,
       default: false,
@@ -63,46 +67,63 @@ export default {
   },
   created() {
     if (!this.disabled) {
-      this.$v.user.$touch();
+      this.$v.$touch();
     }
   },
   data() {
-    return {
-      user: this.cloneValue(),
-    };
+    return {};
+  },
+  computed: {
+    ...mapGetters("userProfile", ["user"]),
+    first_name: {
+      get() {
+        return this.user.first_name;
+      },
+      set(first_name) {
+        this.setFirstName({ first_name });
+      },
+    },
+    last_name: {
+      get() {
+        return this.user.last_name;
+      },
+      set(last_name) {
+        this.setLastName({ last_name });
+      },
+    },
+    email: {
+      get() {
+        return this.user.email;
+      },
+      set(email) {
+        this.setEmail({ email });
+      },
+    },
+    valid() {
+      return !this.$v.$invalid;
+    }
   },
   validations() {
     return {
-      user: {
-        first_name: {
-          required,
-        },
-        last_name: {
-          required,
-        },
-        email: {
-          required,
-          email,
-        },
+      first_name: {
+        required,
+      },
+      last_name: {
+        required,
+      },
+      email: {
+        required,
+        email,
       },
     };
   },
   methods: {
-    cloneValue() {
-      return JSON.parse(JSON.stringify(this.value));
-    },
+    ...mapMutations("userProfile", ["setFirstName", "setLastName", 
"setEmail"]),
     save() {
-      if (!this.$v.$invalid) {
-        this.$emit("save", this.user);
-      }
+      this.$emit("save");
     },
     validateState: errors.vuelidateHelpers.validateState,
   },
-  watch: {
-    value() {
-      this.user = this.cloneValue();
-    },
-  },
 };
 </script>
 
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
 
b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index 647a60aa..65499d58 100644
--- 
a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -17,10 +17,9 @@
       >Please complete your user profile before continuing.</b-alert
     >
     <user-profile-editor
-      v-if="user"
-      v-model="user"
+      ref="userProfileEditor"
       @save="onSave"
-      @resend-email-verification="resendEmailVerification"
+      @resend-email-verification="handleResendEmailVerification"
     />
     <b-link
       v-if="user && user.complete"
@@ -32,36 +31,46 @@
 </template>
 
 <script>
-import { services } from "django-airavata-api";
 import UserProfileEditor from "../components/UserProfileEditor.vue";
 import { notifications } from "django-airavata-common-ui";
+import { mapActions, mapGetters } from "vuex";
 
 export default {
   components: { UserProfileEditor },
   name: "user-profile-container",
-  created() {
-    services.UserService.current()
-      .then((user) => {
-        this.user = user;
-      })
-      .then(() => {
-        const queryParams = new URLSearchParams(window.location.search);
-        if (queryParams.has("code")) {
-          this.verifyEmailChange(queryParams.get("code"));
-        }
-      });
+  async created() {
+    await this.loadCurrentUser();
+
+    const queryParams = new URLSearchParams(window.location.search);
+    if (queryParams.has("code")) {
+      await this.verifyEmailChange({ code: queryParams.get("code") });
+      notifications.NotificationList.add(
+        new notifications.Notification({
+          type: "SUCCESS",
+          message: "Email address verified and updated",
+          duration: 5,
+        })
+      );
+      // Update URL, removing the code from the query string
+      window.history.replaceState({}, "", "/auth/user-profile/");
+    }
   },
   data() {
-    return {
-      user: null,
-    };
+    return {};
+  },
+  computed: {
+    ...mapGetters("userProfile", ["user"]),
   },
   methods: {
-    onSave(value) {
-      services.UserService.update({
-        lookup: value.id,
-        data: value,
-      }).then((user) => {
+    ...mapActions("userProfile", [
+      "loadCurrentUser",
+      "verifyEmailChange",
+      "updateUser",
+      "resendEmailVerification",
+    ]),
+    async onSave() {
+      if (this.$refs.userProfileEditor.valid) {
+        await this.updateUser();
         notifications.NotificationList.add(
           new notifications.Notification({
             type: "SUCCESS",
@@ -69,39 +78,17 @@ export default {
             duration: 5,
           })
         );
-        this.user = user;
-      });
+      }
     },
-    resendEmailVerification() {
-      services.UserService.resendEmailVerification({
-        lookup: this.user.id,
-      }).then(() => {
-        notifications.NotificationList.add(
-          new notifications.Notification({
-            type: "SUCCESS",
-            message: "Verification link sent",
-            duration: 5,
-          })
-        );
-      });
-    },
-    verifyEmailChange(code) {
-      services.UserService.verifyEmailChange({
-        lookup: this.user.id,
-        data: { code: code },
-      }).then((user) => {
-        // User now updated with email change
-        this.user = user;
-        notifications.NotificationList.add(
-          new notifications.Notification({
-            type: "SUCCESS",
-            message: "Email address verified and updated",
-            duration: 5,
-          })
-        );
-        // Update URL, removing the code from the query string
-        window.history.replaceState({}, "", "/auth/user-profile/");
-      });
+    async handleResendEmailVerification() {
+      await this.resendEmailVerification();
+      notifications.NotificationList.add(
+        new notifications.Notification({
+          type: "SUCCESS",
+          message: "Verification link sent",
+          duration: 5,
+        })
+      );
     },
   },
 };
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
 
b/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
index 1052e618..182c6ced 100644
--- 
a/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
@@ -1,8 +1,11 @@
 import { components, entry } from "django-airavata-common-ui";
 import UserProfileContainer from "./containers/UserProfileContainer.vue";
+import createStore from "./store";
 
 entry(Vue => {
+  const store = createStore(Vue);
     new Vue({
-        render: h => h(components.MainLayout, [h(UserProfileContainer)])
+      store,
+      render: (h) => h(components.MainLayout, [h(UserProfileContainer)]),
     }).$mount("#user-profile");
 });
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js 
b/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
new file mode 100644
index 00000000..bb13e8ac
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
@@ -0,0 +1,16 @@
+import Vuex from "vuex";
+import userProfile from "./modules/userProfile";
+
+const debug = process.env.NODE_ENV !== "production";
+
+function createStore(Vue) {
+  Vue.use(Vuex);
+  return new Vuex.Store({
+    modules: {
+      userProfile,
+    },
+    strict: debug,
+  });
+}
+
+export default createStore;
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
 
b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
new file mode 100644
index 00000000..e69de29b
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/userProfile.js
 
b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/userProfile.js
new file mode 100644
index 00000000..9b629ef2
--- /dev/null
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/userProfile.js
@@ -0,0 +1,64 @@
+import { services } from "django-airavata-api";
+
+const state = () => ({
+  user: null,
+});
+
+const getters = {
+  user: (state) => state.user,
+};
+
+const actions = {
+  async loadCurrentUser({ commit }) {
+    const user = await services.UserService.current();
+    commit("setUser", { user });
+  },
+
+  async verifyEmailChange({ commit, state }, { code }) {
+    const user = await services.UserService.verifyEmailChange({
+      lookup: state.user.id,
+      data: { code },
+    });
+    commit("setUser", { user });
+  },
+
+  async updateUser({ commit, state }) {
+    const user = await services.UserService.update({
+      lookup: state.user.id,
+      data: state.user,
+    });
+    commit("setUser", { user });
+  },
+
+  async resendEmailVerification({ state }) {
+    await services.UserService.resendEmailVerification({
+      lookup: state.user.id,
+    });
+  },
+};
+
+const mutations = {
+  setUser(state, { user }) {
+    state.user = user;
+  },
+
+  setFirstName(state, { first_name }) {
+    state.user.first_name = first_name;
+  },
+
+  setLastName(state, { last_name }) {
+    state.user.last_name = last_name;
+  },
+
+  setEmail(state, { email }) {
+    state.user.email = email;
+  },
+};
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  actions,
+  mutations,
+};
diff --git a/django_airavata/apps/auth/yarn.lock 
b/django_airavata/apps/auth/yarn.lock
index eadbe094..817a0c72 100644
--- a/django_airavata/apps/auth/yarn.lock
+++ b/django_airavata/apps/auth/yarn.lock
@@ -8272,6 +8272,11 @@ vuelidate@^0.7.6:
   resolved 
"https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.6.tgz#84100c13b943470660d0416642845cd2a1edf4b2";
   integrity 
sha512-suzIuet1jGcyZ4oUSW8J27R2tNrJ9cIfklAh63EbAkFjE380iv97BAiIeolRYoB9bF9usBXCu4BxftWN1Dkn3g==
 
+vuex@^3.6.2:
+  version "3.6.2"
+  resolved 
"https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71";
+  integrity 
sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
+
 watchpack-chokidar2@^2.0.0:
   version "2.0.0"
   resolved 
"https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0";

Reply via email to