Skip to content
Snippets Groups Projects
Select Git revision
  • Issue/2935-sideUploadFix
  • main default protected
  • dev protected
  • Issue/3090-tosProblems
  • Issue/3178-iconColorBug
  • Issue/3176-addNewNFDI4INGLogo
  • Issue/3141-rdsNoLonga
  • Issue/3180-fixMetadataNotLoading
  • Issue/3177-resourceTypeDescriptionTexts
  • Issue/3160-deactivateDownloadForFolders
  • Issue/3111-fixLoadingGitLabResource
  • Issue/3133-subProjectsChanges
  • Issue/3139-dsnrw
  • Issue/3167-changeTextAndAddLink
  • Issue/3070-newIconsForResourceTypes
  • Issue/3145-redesignLoginPage
  • Issue/3093-moreInformationInTheDeletionEmails
  • Issue/3040-closeTokenWindowWithXButton
  • Issue/3152-fixResourceStore
  • Issue/xxxx-DuckDBTest
  • Issue/3152-fixUpdateRequestPayload
  • v3.19.1
  • v3.19.0
  • v3.18.0
  • v3.17.2
  • v3.17.1
  • v3.17.0
  • v3.16.1
  • v3.16.0
  • v3.15.6
  • v3.15.5
  • v3.15.4
  • v3.15.3
  • v3.15.2
  • v3.15.1
  • v3.15.0
  • v3.14.0
  • v3.13.1
  • v3.13.0
  • v3.12.0
  • v3.11.0
41 results

README.md

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    UserProfile.vue 18.87 KiB
    <template>
      <b-row>
        <div class="col-sm-2" />
        <div class="col-sm-8">
          <b-form @submit.stop.prevent="onSubmit">
            <!-- Personal Information -->
            <CoscineHeadline
              :headline="$t('page.userprofile.form.personalInformation.header')"
            />
            <!-- Title -->
            <coscine-form-group
              label-for="title"
              :label="
                $t('page.userprofile.form.personalInformation.labels.titleLabel')
              "
              :is-loading="isLoading"
              type="input"
            >
              <multiselect
                id="title"
                v-model="$v.form.title.$model"
                :options="titles"
                :multiple="false"
                label="displayName"
                track-by="displayName"
                :show-labels="false"
                :placeholder="
                  $t(
                    'page.userprofile.form.personalInformation.multiselect.placeholderTitle'
                  )
                "
                :select-label="
                  $t(
                    'page.userprofile.form.personalInformation.multiselect.selectEnter'
                  )
                "
              >
                <span slot="noResult">
                  {{
                    $t(
                      "page.userprofile.form.personalInformation.multiselect.noResults"
                    )
                  }}
                </span>
                <span slot="noOptions">
                  {{
                    $t(
                      "page.userprofile.form.personalInformation.multiselect.noOptions"
                    )
                  }}
                </span>
              </multiselect>
            </coscine-form-group>
            <!-- Given Name -->
            <coscine-form-group
              :mandatory="true"
              label-for="givenname"
              :label="
                $t(
                  'page.userprofile.form.personalInformation.labels.givenNameLabel'
                )
              "
              :is-loading="isLoading"
              type="input"
            >
              <b-form-input
                id="givenname"
                v-model="$v.form.givenname.$model"
                :state="
                  $v.form.givenname.$dirty ? !$v.form.givenname.$invalid : null
                "
                :placeholder="
                  $t('page.userprofile.form.personalInformation.givenName')
                "
              />
            </coscine-form-group>
            <!-- Surname -->
            <coscine-form-group
              :mandatory="true"
              label-for="surname"
              :label="
                $t('page.userprofile.form.personalInformation.labels.surnameLabel')
              "
              :is-loading="isLoading"
              type="input"
            >
              <b-form-input
                id="surname"
                v-model="$v.form.surname.$model"
                :state="$v.form.surname.$dirty ? !$v.form.surname.$invalid : null"
                :placeholder="
                  $t('page.userprofile.form.personalInformation.surname')
                "
              />
            </coscine-form-group>
            <!-- Email -->
            <coscine-form-group
              :mandatory="true"
              label-for="Email"
              :label="
                $t('page.userprofile.form.personalInformation.labels.emailLabel')
              "
              :is-loading="isLoading"
              type="input"
            >
              <b-form-input
                id="Email"
                v-model="$v.form.emailAddress.$model"
                :state="
                  $v.form.emailAddress.$dirty || !profileForm.form.emailAddress
                    ? !$v.form.emailAddress.$invalid
                    : null
                "
                :placeholder="$t('page.userprofile.form.personalInformation.email')"
              />
              <div id="emailHint">{{ emailHint }}</div>
            </coscine-form-group>
            <!-- Organization -->
            <coscine-form-group
              :mandatory="true"
              label-for="organization"
              :label="
                $t(
                  'page.userprofile.form.personalInformation.labels.organizationLabel'
                )
              "
              :is-loading="isLoading"
              type="input"
            >
              <!-- Organization - External User -->
              <multiselect
                v-if="externalUser"
                id="organization"
                v-model="$v.form.organization.$model"
                :options="ror"
                :multiple="false"
                :loading="loadingOrganizations"
                :show-labels="false"
                :placeholder="
                  $t(
                    'page.userprofile.form.personalInformation.multiselect.placeholderText'
                  )
                "
                @search-change="triggerFetchOptions"
              >
                <template slot="noOptions">
                  {{
                    $t(
                      "page.userprofile.form.personalInformation.multiselect.noOptionsOrganization"
                    )
                  }}
                </template>
              </multiselect>
    
              <!-- Organization - Internal User -->
              <multiselect
                v-else
                id="organization"
                v-model="organizations"
                :options="organizations"
                :multiple="true"
                :show-labels="false"
                :placeholder="
                  $t('page.userprofile.form.personalInformation.organization')
                "
                :disabled="true"
              />
            </coscine-form-group>
            <!-- Institute -->
            <coscine-form-group
              :mandatory="true"
              label-for="institute"
              :label="
                $t(
                  'page.userprofile.form.personalInformation.labels.instituteLabel'
                )
              "
              :is-loading="isLoading"
              type="input"
            >
              <!-- Institute - External User -->
              <b-form-input
                v-if="externalUser"
                id="institute"
                v-model="$v.form.institute.$model"
                type="text"
                :state="
                  $v.form.institute.$dirty ? !$v.form.institute.$invalid : null
                "
                :placeholder="
                  $t('page.userprofile.form.personalInformation.institute')
                "
              />
              <!-- Institute - Internal User -->
              <multiselect
                v-else
                id="institute"
                v-model="institutes"
                :options="institutes"
                :multiple="true"
                :show-labels="false"
                :placeholder="
                  $t('page.userprofile.form.personalInformation.institute')
                "
                :disabled="true"
              />
            </coscine-form-group>
            <!-- Discipline -->
            <coscine-form-group
              :mandatory="true"
              label-for="Discipline"
              :label="
                $t(
                  'page.userprofile.form.personalInformation.labels.disciplineLabel'
                )
              "
              :is-loading="isLoading"
              type="input"
            >
              <multiselect
                id="Discipline"
                v-model="$v.form.disciplines.$model"
                :options="disciplines"
                :multiple="true"
                :label="disciplineLabel"
                :track-by="disciplineLabel"
                :show-labels="false"
                :placeholder="
                  $t(
                    'page.userprofile.form.personalInformation.multiselect.placeholderDiscipline'
                  )
                "
              >
                <template slot="singleLabel" slot-scope="props">
                  {{ props.option[disciplineLabel] }}
                </template>
                <template slot="option" slot-scope="props">
                  {{ props.option[disciplineLabel] }}
                </template>
              </multiselect>
            </coscine-form-group>
    
            <!-- Access Token -->
            <div class="h-divider" />
            <CoscineHeadline
              :headline="$t('page.userprofile.form.accessToken.header')"
            />
            <AccessToken />
    
            <!-- User Preferences -->
            <div class="h-divider" />
            <CoscineHeadline
              :headline="$t('page.userprofile.form.userPreferences.header')"
            />
            <!-- Language -->
            <coscine-form-group
              :mandatory="true"
              label-for="language"
              :label="
                $t('page.userprofile.form.userPreferences.labels.languageLabel')
              "
              :is-loading="isLoading"
              type="button"
            >
              <b-form-radio-group
                id="language"
                class="bv-no-focus-ring"
                :checked="
                  profileForm.form.language && profileForm.form.language.id
                    ? profileForm.form.language.id
                    : false
                "
                :state="
                  profileForm.form.language && profileForm.form.language.id
                    ? null
                    : false
                "
                :options="languages"
                name="radios-stacked"
                text-field="displayName"
                value-field="id"
                stacked
                @change="($event) => ($v.form.language.$model = { id: $event })"
              />
            </coscine-form-group>
    
            <!-- Connected Accounts -->
            <div class="h-divider" />
            <CoscineHeadline
              :headline="$t('page.userprofile.form.connectedAccounts.header')"
            />
            <coscine-form-group
              :mandatory="true"
              :label="
                $t('page.userprofile.form.connectedAccounts.labels.orcidLabel')
              "
              :is-loading="isLoading"
              type="button"
            >
              <b-button
                variant="secondary"
                class="float-left"
                name="orcidConnect"
                :disabled="orcidConnected"
                @click.prevent="clickConnect('orcid')"
              >
                {{
                  orcidConnected ? $t("buttons.connected") : $t("buttons.connect")
                }}
              </b-button>
            </coscine-form-group>
            <coscine-form-group
              :mandatory="true"
              :label="
                $t('page.userprofile.form.connectedAccounts.labels.shibbolethLabel')
              "
              :is-loading="isLoading"
              type="button"
            >
              <b-button
                variant="secondary"
                class="float-left"
                name="shibbolethConnect"
                :disabled="shibbolethConnected"
                @click.prevent="clickConnect('shibboleth')"
              >
                {{
                  shibbolethConnected
                    ? $t("buttons.connected")
                    : $t("buttons.connect")
                }}
              </b-button>
            </coscine-form-group>
    
            <!-- Save Button -->
            <coscine-form-group>
              <b-button
                ref="saveBtn"
                type="submit"
                variant="primary"
                class="float-right"
                name="save"
                :disabled="$v.form.$invalid || savingProfile || !$v.form.$anyDirty"
                @click.prevent="clickSave"
              >
                {{ $t("buttons.save") }}
              </b-button>
            </coscine-form-group>
          </b-form>
        </div>
        <div class="col-sm-2" />
      </b-row>
    </template>
    
    <script lang="ts">
    import { defineComponent, reactive } from "vue-demi";
    import { required, email } from "@vuelidate/validators";
    import { useVuelidate, Validation } from "@vuelidate/core";
    import AccessToken from "./components/AccessToken.vue";
    
    import "@/plugins/deprecated/vue-multiselect";
    
    // import the main store
    import useMainStore from "@/store/index";
    // import the store for current module
    import useUserStore from "../store";
    import type {
      ContactChangeObject,
      DisciplineObject,
      LanguageObject,
      TitleObject,
      UserObject,
    } from "@coscine/api-client/dist/types/Coscine.Api.User";
    import type { OrganizationObject } from "@coscine/api-client/dist/types/Coscine.Api.Project";
    import useNotificationStore from "@/store/notification";
    
    export default defineComponent({
      components: { AccessToken },
      setup() {
        const mainStore = useMainStore();
        const userStore = useUserStore();
        const notificationStore = useNotificationStore();
        /*
          Definition of the validation rules and initial state
          will enable proper typings in the code
        */
        const profileForm = reactive({
          form: {
            title: "",
            givenname: "",
            surname: "",
            emailAddress: "",
            organization: "",
            institute: "",
            disciplines: [] as DisciplineObject[],
            language: {} as LanguageObject,
          } as UserObject,
        });
        const profileRules = {
          form: {
            title: {},
            givenname: { required },
            surname: { required },
            emailAddress: { email, required },
            organization: { required },
            institute: { required },
            disciplines: { required },
            language: { id: {} },
          },
        };
        const $v = useVuelidate(profileRules, profileForm) as Validation<
          typeof profileRules
        >;
        return { mainStore, userStore, notificationStore, $v, profileForm };
      },
      data() {
        return {
          isLoading: false,
          savingProfile: false,
          currentUserComponent: "UserProfileComponent",
          disciplineLabel: "displayNameEn",
          selectedExternalOrganization: null as Record<string, unknown> | null,
          queryTimer: 0,
          loadingOrganizations: false,
        };
      },
      computed: {
        contactChange(): null | ContactChangeObject[] {
          return this.userStore.userProfile.contactChange;
        },
        disciplines(): null | DisciplineObject[] {
          return this.userStore.userProfile.disciplines;
        },
        emailHint(): string {
          if (!this.profileForm.form.emailAddress) {
            return this.$t(
              "page.userprofile.form.personalInformation.emailChange.noAddress"
            ).toString();
          } else if (this.contactChange !== null && this.contactChange.length > 0) {
            return this.$t(
              "page.userprofile.form.personalInformation.emailChange.pendingConfirmation"
            ).toString();
          }
          return "";
        },
        externalUser(): boolean {
          return !(
            this.organizations &&
            this.institutes &&
            this.organizations.length > 0 &&
            this.institutes.length > 0
          );
        },
        institutes(): string[] {
          if (this.userMemberships) {
            return this.userMemberships
              .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 [];
        },
        languages(): null | LanguageObject[] {
          return this.userStore.userProfile.languages;
        },
        userMemberships(): null | OrganizationObject[] {
          return this.userStore.userProfile.userMemberships;
        },
        orcidConnected(): boolean {
          if (this.user.externalAuthenticators) {
            return this.user.externalAuthenticators.some(
              (externalAuthenticator) =>
                externalAuthenticator.displayName &&
                externalAuthenticator.displayName.toLowerCase() === "orcid"
            );
          }
          return false;
        },
        organizations(): string[] {
          if (this.userMemberships) {
            return this.userMemberships
              .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 | 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) {
            return this.user.externalAuthenticators.some(
              (externalAuthenticator) =>
                externalAuthenticator.displayName &&
                externalAuthenticator.displayName.toLowerCase() === "shibboleth"
            );
          }
          return false;
        },
        titles(): null | TitleObject[] {
          return this.userStore.userProfile.titles;
        },
        user(): UserObject {
          return this.userStore.user ? this.userStore.user : {};
        },
      },
      watch: {
        user() {
          this.cloneUser();
        },
      },
      created() {
        this.cloneUser();
        if (this.user.language === "en") {
          this.disciplineLabel = "displayNameEn";
        } else {
          this.disciplineLabel = "displayNameDe";
        }
      },
      methods: {
        cloneUser() {
          if (this.user !== null) {
            Object.assign(this.profileForm.form, this.user);
          }
        },
        async clickConnect(provider: string) {
          await this.userStore.storeMergeToken(provider);
          location.href = "/coscine/api/Coscine.Api.STS/account/logout";
        },
        async clickSave() {
          this.$v.form.$touch();
          if (!this.$v.form.$invalid && 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.profileForm.form.emailAddress &&
              this.profileForm.form.emailAddress !== this.user.emailAddress
            ) {
              await this.userStore.updateEmail(this.profileForm.form.emailAddress);
              await Promise.all([
                this.userStore.retrieveContactChange(),
                this.userStore.retrieveUser(),
              ]);
              this.notificationStore.postNotification({
                title: this.$t(
                  "page.userprofile.form.personalInformation.emailChange.changedTitle"
                ).toString(),
                body: this.$t(
                  "page.userprofile.form.personalInformation.emailChange.changedMessage"
                ).toString(),
              });
            }
            // SECTION FOR SAVING THE REST OF THE FIELDS
            const updatedUser = await this.userStore.updateUser(
              this.profileForm.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(),
              });
            } else {
              // On Failure
              this.notificationStore.postNotification({
                title: this.$t("toast.onSave.failure.title").toString(),
                body: this.$t("toast.onSave.failure.message").toString(),
                variant: "danger",
              });
            }
            await Promise.all([
              this.userStore.retrieveContactChange(),
              this.userStore.retrieveUser(),
            ]);
            this.savingProfile = false;
          }
        },
        triggerFetchOptions(search: string) {
          clearTimeout(this.queryTimer);
          if (search.length < 3) {
            return;
          }
          this.queryTimer = window.setTimeout(
            () => this.fetchOrganizationOptions(search),
            1000
          );
        },
        async fetchOrganizationOptions(search: string) {
          this.loadingOrganizations = true;
          await this.userStore.retrieveOrganizations(search);
          this.loadingOrganizations = false;
        },
      },
    });
    </script>