xref: /openbmc/webui-vue/src/views/ProfileSettings/ProfileSettings.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <b-container fluid="xl">
3    <page-title />
4
5    <b-row>
6      <b-col md="8" lg="8" xl="6">
7        <page-section
8          :section-title="$t('pageProfileSettings.profileInfoTitle')"
9        >
10          <dl>
11            <dt>{{ $t('pageProfileSettings.username') }}</dt>
12            <dd>
13              {{ username }}
14            </dd>
15          </dl>
16        </page-section>
17      </b-col>
18    </b-row>
19
20    <b-form @submit.prevent="submitForm">
21      <!-- Hidden username field for browser autocomplete accessibility -->
22      <input
23        type="text"
24        name="username"
25        :value="username"
26        autocomplete="username"
27        class="visually-hidden"
28        aria-hidden="true"
29        tabindex="-1"
30      />
31      <b-row>
32        <b-col sm="8" md="6" xl="3">
33          <page-section
34            :section-title="$t('pageProfileSettings.changePassword')"
35          >
36            <b-form-group
37              id="input-group-0"
38              :label="$t('pageProfileSettings.currentPassword')"
39              label-for="input-0"
40            >
41              <input-password-toggle>
42                <b-form-input
43                  id="old-password"
44                  v-model="form.currentPassword"
45                  type="password"
46                  autocomplete="current-password"
47                  data-test-id="profileSettings-input-ocurrentPassword"
48                  class="form-control-with-button"
49                />
50              </input-password-toggle>
51            </b-form-group>
52            <b-form-group
53              id="input-group-1"
54              :label="$t('pageProfileSettings.newPassword')"
55              label-for="input-1"
56            >
57              <b-form-text id="password-help-block">
58                {{
59                  $t('pageUserManagement.modal.passwordMustBeBetween', {
60                    min: passwordRequirements.minLength,
61                    max: passwordRequirements.maxLength,
62                  })
63                }}
64              </b-form-text>
65              <input-password-toggle>
66                <b-form-input
67                  id="password"
68                  v-model="form.newPassword"
69                  type="password"
70                  aria-describedby="password-help-block"
71                  autocomplete="new-password"
72                  :state="getValidationState(v$.form.newPassword)"
73                  data-test-id="profileSettings-input-newPassword"
74                  class="form-control-with-button"
75                  @input="v$.form.newPassword.$touch()"
76                />
77                <b-form-invalid-feedback role="alert">
78                  <template
79                    v-if="
80                      v$.form.newPassword.minLength.$invalid ||
81                      v$.form.newPassword.maxLength.$invalid
82                    "
83                  >
84                    {{
85                      $t('pageProfileSettings.newPassLabelTextInfo', {
86                        min: passwordRequirements.minLength,
87                        max: passwordRequirements.maxLength,
88                      })
89                    }}
90                  </template>
91                </b-form-invalid-feedback>
92              </input-password-toggle>
93            </b-form-group>
94            <b-form-group
95              id="input-group-2"
96              :label="$t('pageProfileSettings.confirmPassword')"
97              label-for="input-2"
98            >
99              <input-password-toggle>
100                <b-form-input
101                  id="password-confirmation"
102                  v-model="form.confirmPassword"
103                  type="password"
104                  autocomplete="new-password"
105                  :state="getValidationState(v$.form.confirmPassword)"
106                  data-test-id="profileSettings-input-confirmPassword"
107                  class="form-control-with-button"
108                  @input="v$.form.confirmPassword.$touch()"
109                />
110                <b-form-invalid-feedback role="alert">
111                  <template
112                    v-if="v$.form.confirmPassword.sameAsPassword.$invalid"
113                  >
114                    {{ $t('pageProfileSettings.passwordsDoNotMatch') }}
115                  </template>
116                </b-form-invalid-feedback>
117              </input-password-toggle>
118            </b-form-group>
119          </page-section>
120        </b-col>
121      </b-row>
122      <page-section :section-title="$t('pageProfileSettings.timezoneDisplay')">
123        <p>{{ $t('pageProfileSettings.timezoneDisplayDesc') }}</p>
124        <b-row>
125          <b-col md="9" lg="8" xl="9">
126            <b-form-group :label="$t('pageProfileSettings.timezone')">
127              <b-form-radio
128                v-model="form.isUtcDisplay"
129                :value="true"
130                data-test-id="profileSettings-radio-defaultUTC"
131              >
132                {{ $t('pageProfileSettings.defaultUTC') }}
133              </b-form-radio>
134              <b-form-radio
135                v-model="form.isUtcDisplay"
136                :value="false"
137                data-test-id="profileSettings-radio-browserOffset"
138              >
139                {{
140                  $t('pageProfileSettings.browserOffset', {
141                    timezone,
142                  })
143                }}
144              </b-form-radio>
145            </b-form-group>
146          </b-col>
147        </b-row>
148      </page-section>
149      <b-button
150        variant="primary"
151        type="submit"
152        data-test-id="profileSettings-button-saveSettings"
153      >
154        {{ $t('global.action.saveSettings') }}
155      </b-button>
156    </b-form>
157  </b-container>
158</template>
159
160<script>
161import BVToastMixin from '@/components/Mixins/BVToastMixin';
162import InputPasswordToggle from '@/components/Global/InputPasswordToggle';
163import { maxLength, minLength, sameAs } from '@vuelidate/validators';
164import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
165import LocalTimezoneLabelMixin from '@/components/Mixins/LocalTimezoneLabelMixin';
166import PageTitle from '@/components/Global/PageTitle';
167import PageSection from '@/components/Global/PageSection';
168import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
169import { useVuelidate } from '@vuelidate/core';
170import { useI18n } from 'vue-i18n';
171import i18n from '@/i18n';
172
173export default {
174  name: 'ProfileSettings',
175  components: { InputPasswordToggle, PageSection, PageTitle },
176  mixins: [
177    BVToastMixin,
178    LocalTimezoneLabelMixin,
179    LoadingBarMixin,
180    VuelidateMixin,
181  ],
182  setup() {
183    return {
184      v$: useVuelidate(),
185    };
186  },
187  data() {
188    return {
189      $t: useI18n().t,
190      form: {
191        newPassword: '',
192        confirmPassword: '',
193        currentPassword: '',
194        isUtcDisplay: this.$store.getters['global/isUtcDisplay'],
195      },
196    };
197  },
198  computed: {
199    username() {
200      return this.$store.getters['global/username'];
201    },
202    passwordRequirements() {
203      return this.$store.getters['userManagement/accountPasswordRequirements'];
204    },
205    timezone() {
206      return this.localOffset();
207    },
208  },
209  created() {
210    this.startLoader();
211    this.$store
212      .dispatch('userManagement/getAccountSettings')
213      .finally(() => this.endLoader());
214  },
215  validations() {
216    return {
217      form: {
218        newPassword: {
219          minLength: minLength(this.passwordRequirements.minLength),
220          maxLength: maxLength(this.passwordRequirements.maxLength),
221        },
222        confirmPassword: {
223          sameAsPassword: sameAs(this.form.newPassword),
224        },
225      },
226    };
227  },
228  methods: {
229    saveNewPasswordInputData() {
230      this.v$.form.confirmPassword.$touch();
231      this.v$.form.newPassword.$touch();
232      if (this.v$.$invalid) return;
233      let userData = {
234        originalUsername: this.username,
235        password: this.form.newPassword,
236      };
237
238      this.$store
239        .dispatch('userManagement/updateUser', userData)
240        .then((message) => {
241          (this.form.newPassword = ''),
242            (this.form.confirmPassword = ''),
243            (this.form.currentPassword = '');
244          this.v$.$reset();
245          this.successToast(message);
246          this.$store.dispatch('authentication/logout');
247        })
248        .catch(({ message }) => this.errorToast(message));
249    },
250    saveTimeZonePrefrenceData() {
251      localStorage.setItem('storedUtcDisplay', this.form.isUtcDisplay);
252      this.$store.commit('global/setUtcTime', this.form.isUtcDisplay);
253      this.successToast(
254        i18n.global.t('pageProfileSettings.toast.successUpdatingTimeZone'),
255      );
256    },
257    submitForm() {
258      if (
259        this.form.confirmPassword &&
260        this.form.newPassword &&
261        this.form.currentPassword
262      ) {
263        this.confirmAuthenticate();
264      }
265      if (
266        this.$store.getters['global/isUtcDisplay'] != this.form.isUtcDisplay
267      ) {
268        this.saveTimeZonePrefrenceData();
269      }
270    },
271    confirmAuthenticate() {
272      this.v$.form.newPassword.$touch();
273      if (this.v$.$invalid) return;
274
275      const username = this.username;
276      const password = this.form.currentPassword;
277
278      this.$store
279        .dispatch('authentication/login', { username, password })
280        .then(() => {
281          this.saveNewPasswordInputData();
282        })
283        .catch(() => {
284          this.v$.$reset();
285          this.errorToast(
286            i18n.global.t('pageProfileSettings.toast.wrongCredentials'),
287          );
288        });
289    },
290  },
291};
292</script>
293