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"