From 384c3ff26c9985a5fa82585f43bbab0f9213a0a5 Mon Sep 17 00:00:00 2001
From: Petar Hristov <hristov@itc.rwth-aachen.de>
Date: Tue, 31 May 2022 09:55:26 +0200
Subject: [PATCH] Fix: Retrun objects for institutes and organizations
 (coscine/issues#1963)

---
 src/modules/user/pages/UserProfile.vue | 118 ++++++++++++-------------
 src/modules/user/store.ts              |   4 +-
 src/plugins/deprecated/_custom.css     |  51 ++++++-----
 3 files changed, 86 insertions(+), 87 deletions(-)

diff --git a/src/modules/user/pages/UserProfile.vue b/src/modules/user/pages/UserProfile.vue
index 83822c24..2c6f2482 100644
--- a/src/modules/user/pages/UserProfile.vue
+++ b/src/modules/user/pages/UserProfile.vue
@@ -62,7 +62,9 @@
           <b-form-input
             id="givenname"
             v-model="$v.form.givenname.$model"
-            :state="$v.form.givenname.$dirty ? !$v.form.givenname.$error : null"
+            :state="
+              $v.form.givenname.$dirty ? !$v.form.givenname.$invalid : null
+            "
             :placeholder="
               $t('page.userprofile.form.personalInformation.givenName')
             "
@@ -81,7 +83,7 @@
           <b-form-input
             id="surname"
             v-model="$v.form.surname.$model"
-            :state="$v.form.surname.$dirty ? !$v.form.surname.$error : null"
+            :state="$v.form.surname.$dirty ? !$v.form.surname.$invalid : null"
             :placeholder="
               $t('page.userprofile.form.personalInformation.surname')
             "
@@ -101,7 +103,9 @@
             id="Email"
             v-model="$v.form.emailAddress.$model"
             :state="
-              $v.form.emailAddress.$dirty ? !$v.form.emailAddress.$error : null
+              $v.form.emailAddress.$dirty || !form.emailAddress
+                ? !$v.form.emailAddress.$invalid
+                : null
             "
             :placeholder="$t('page.userprofile.form.personalInformation.email')"
           />
@@ -126,8 +130,6 @@
             :options="ror"
             :multiple="false"
             :loading="loadingOrganizations"
-            label="displayName"
-            track-by="displayName"
             :show-labels="false"
             :placeholder="
               $t(
@@ -136,12 +138,6 @@
             "
             @search-change="triggerFetchOptions"
           >
-            <template slot="singleLabel" slot-scope="props">
-              {{ props.option.displayName }}
-            </template>
-            <template slot="option" slot-scope="props">
-              {{ props.option.displayName }}
-            </template>
             <template slot="noOptions">
               {{
                 $t(
@@ -251,6 +247,7 @@
         <CoscineHeadline
           :headline="$t('page.userprofile.form.userPreferences.header')"
         />
+        <!-- Language -->
         <coscine-form-group
           :mandatory="true"
           label-for="language"
@@ -263,7 +260,10 @@
           <b-form-radio-group
             id="language"
             class="bv-no-focus-ring"
-            :checked="form.language !== undefined ? form.language.id : false"
+            :checked="
+              form.language && form.language.id ? form.language.id : false
+            "
+            :state="form.language && form.language.id ? null : false"
             :options="languages"
             name="radios-stacked"
             text-field="displayName"
@@ -330,38 +330,8 @@
             :disabled="$v.form.$invalid || savingProfile || !$v.form.$anyDirty"
             @click.prevent="clickSave"
           >
-            <b-iconstack
-              v-if="savingProfile"
-              animation="spin"
-              aria-hidden="true"
-            >
-              <b-icon
-                stacked
-                icon="dot"
-                aria-hidden="true"
-                shift-v="4"
-              ></b-icon>
-              <b-icon
-                stacked
-                icon="dot"
-                aria-hidden="true"
-                shift-v="-4"
-              ></b-icon>
-              <b-icon
-                stacked
-                icon="dot"
-                aria-hidden="true"
-                shift-h="4"
-              ></b-icon>
-              <b-icon
-                stacked
-                icon="dot"
-                aria-hidden="true"
-                shift-h="-4"
-              ></b-icon>
-            </b-iconstack>
-            {{ $t("buttons.save") }}</b-button
-          >
+            {{ $t("buttons.save") }}
+          </b-button>
         </coscine-form-group>
       </b-form>
     </div>
@@ -422,9 +392,15 @@ export default defineComponent({
         email,
         required,
       },
-      organization: {},
-      institute: {},
-      disciplines: {},
+      organization: {
+        required,
+      },
+      institute: {
+        required,
+      },
+      disciplines: {
+        required,
+      },
       language: {
         id: {},
       },
@@ -469,11 +445,12 @@ export default defineComponent({
         this.institutes.length > 0
       );
     },
-    institutes(): OrganizationObject[] {
+    institutes(): string[] {
       if (this.memberOrganizations) {
-        return this.memberOrganizations.filter(
-          (organization) => organization.url?.indexOf("#") !== -1
-        );
+        return this.memberOrganizations
+          .filter((organization) => organization.url?.indexOf("#") !== -1) // If does contain "#" it's a sub level organization, otherwise top level
+          .map((org) => (org.displayName ? org.displayName : "")) // Extract organization display name, could contain empty strings
+          .filter((n) => n); // Filter out empty strings, if any;;
       }
       return [];
     },
@@ -493,16 +470,22 @@ export default defineComponent({
       }
       return false;
     },
-    organizations(): OrganizationObject[] {
+    organizations(): string[] {
       if (this.memberOrganizations) {
-        this.memberOrganizations.filter(
-          (organization) => !(organization.url?.indexOf("#") !== -1)
-        );
+        return this.memberOrganizations
+          .filter((organization) => !(organization.url?.indexOf("#") !== -1)) // If does contain "#" it's a sub level organization, otherwise top level
+          .map((org) => (org.displayName ? org.displayName : "")) // Extract organization display name, could contain empty strings
+          .filter((n) => n); // Filter out empty strings, if any;
       }
       return [];
     },
-    ror(): null | OrganizationObject[] {
-      return this.userStore.userProfile.organizations;
+    ror(): null | string[] {
+      const organizations = this.userStore.userProfile.organizations;
+      if (organizations) {
+        return organizations
+          .map((org) => (org.displayName ? org.displayName : "")) // Extract organization display name, could contain empty strings
+          .filter((n) => n); // Filter out empty strings, if any
+      } else return null;
     },
     shibbolethConnected(): boolean {
       if (this.user.externalAuthenticators) {
@@ -552,6 +535,9 @@ export default defineComponent({
         this.$v.form.$anyDirty
       ) {
         this.savingProfile = true;
+        // SECTION FOR SAVING ANY EMAIL CHANGES
+        // SIDE EFFECT: NO OTHER FIELD CHANGE CAN BE SAVED TOGETHER WITH A CHANGE OF THE EMAIL ADDRESS
+        // TODO
         if (
           this.form.emailAddress &&
           this.form.emailAddress !== this.user.emailAddress
@@ -570,22 +556,28 @@ export default defineComponent({
             ).toString(),
           });
         }
-        try {
-          await this.userStore.updateUser(this.form);
-          this.$v.form.$reset();
+        // SECTION FOR SAVING THE REST OF THE FIELDS
+        const updatedUser = await this.userStore.updateUser(this.form);
+        if (updatedUser) {
+          // On Success
+          this.$v.form.$reset()
           this.notificationStore.postNotification({
             title: this.$t("toast.onSave.success.title").toString(),
             body: this.$t("toast.onSave.success.message").toString(),
           });
-        } catch {
+        } else {
+          this.savingProfile = false;
+          // On Failure
           this.notificationStore.postNotification({
             title: this.$t("toast.onSave.failure.title").toString(),
             body: this.$t("toast.onSave.failure.message").toString(),
             variant: "danger",
           });
-        } finally {
-          this.savingProfile = false;
         }
+        await Promise.all([
+            this.userStore.retrieveContactChange(),
+            this.userStore.retrieveUser(),
+          ]);
       }
     },
     triggerFetchOptions(search: string) {
diff --git a/src/modules/user/store.ts b/src/modules/user/store.ts
index 6af09e98..f4058017 100644
--- a/src/modules/user/store.ts
+++ b/src/modules/user/store.ts
@@ -254,13 +254,15 @@ export const useUserStore = defineStore({
       }
     },
 
-    async updateUser(user: UserObject) {
+    async updateUser(user: UserObject): Promise<boolean> {
       const notificationStore = useNotificationStore();
       try {
         await UserApi.userUpdateUser(user);
+        return true;
       } catch (error) {
         // Handle other Status Codes
         notificationStore.postApiErrorNotification(error as AxiosError);
+        return false;
       }
     },
 
diff --git a/src/plugins/deprecated/_custom.css b/src/plugins/deprecated/_custom.css
index 730c3b06..2b69513e 100644
--- a/src/plugins/deprecated/_custom.css
+++ b/src/plugins/deprecated/_custom.css
@@ -1,10 +1,10 @@
 .multiselect__placeholder {
-  color: var(--gray) !important;
+  color: var(--gray);
   /* Value taken from bootstrap */
   font-size: 1rem;
   /* All bellow is to center the gray placeholder vertically */
-  padding: 0px !important;
-  margin: 0 !important;
+  padding: 0px;
+  margin: 0;
   position: absolute;
   top: 50%;
   -ms-transform: translateY(-50%);
@@ -13,59 +13,64 @@
 
 .multiselect__option--highlight {
   /* Color for when an option IS NOT selected and hovered on */
-  background: var(--primary) !important;
+  background: var(--primary);
 }
 
 .multiselect__option--selected.multiselect__option--highlight,
 .multiselect__option--highlight:after {
   /* Color for when an option IS selected and hovered on */
-  background: var(--primary) !important;
+  background: var(--primary);
 }
 
 .multiselect__input,
 .multiselect__single {
-  padding: 0px !important;
-  margin: 0px !important;
+  padding: 0px;
+  margin: 0px;
   /* All bellow is to center the placeholder vertically when active */
-  -ms-transform: translateY(15%) !important;
-  transform: translateY(15%) !important;
+  -ms-transform: translateY(15%);
+  transform: translateY(15%);
 }
 
 .multiselect__tags {
   /* Values taken from bootstrap */
-  padding-left: 0.75rem !important;
-  padding-top: 0.375rem !important;
-  padding-bottom: 0.375rem !important;
-  min-height: calc(1.5rem + 0.75rem + 2px) !important;
+  padding-left: 0.75rem;
+  padding-top: 0.375rem;
+  padding-bottom: 0.375rem;
+  min-height: calc(1.5rem + 0.75rem + 2px);
+  background-color: transparent;
 }
 
 .multiselect__tags-wrap {
-  margin: 0 !important;
-  padding: 0 !important;
+  margin: 0;
+  padding: 0;
 }
 
 .multiselect__tag {
-  background: var(--primary) !important;
-  color: var(--white) !important;
-  margin-bottom: 0px !important;
-  margin-right: 5px !important;
+  background: var(--primary);
+  color: var(--white);
+  margin-bottom: 0px;
+  margin-right: 5px;
 }
 
 .multiselect__tag-icon:after {
-  color: var(--secondary) !important;
+  color: var(--secondary);
 }
 
 .multiselect__spinner:before,
 .multiselect__spinner:after {
-  border-color: var(--primary) transparent transparent !important;
+  border-color: var(--primary) transparent transparent;
 }
 
 .multiselect__tag-icon:focus,
 .multiselect__tag-icon:hover {
-  background: transparent !important;
+  background: transparent;
 }
 
 .multiselect__tag-icon:focus:after,
 .multiselect__tag-icon:hover:after {
-  color: var(--white) !important;
+  color: var(--white);
+}
+
+.multiselect--disabled {
+  border-radius: 0.25rem;
 }
\ No newline at end of file
-- 
GitLab