<template> <div> <b-button id="back-button" variant="primary" data-test="userProfile-goBackToNext" @click="goBackToNext" > <b-icon-arrow-bar-left/> {{ $t('words.goBack') }} </b-button> <div class="signup-container"> <div class="app-header"> <img alt="logo" :src="logoPath"/> </div> <div class="signup-form"> <h4 class="title"> {{ $t('profile.title') }} <span class="sub-title">{{ $t('profile.subtitle') }}</span> </h4> <hr class="divider"> <ValidationObserver ref="form" v-slot="{ handleSubmit }"> <b-overlay :show="loadingUserInformation" rounded="lg" :style="'padding: 5px;'" variant="white" > <form> <h5>{{ $t('profile.form.personalDetails.title') }}</h5> <hr class="divider"> <div class="row g-2 align-items-center"> <div class="col-3"> <label class="col-form-label">{{ $t('words.username') }}</label> </div> <div class="col"> <div class="input-group flex-nowrap"> <input data-test="userProfile-username" v-model="formUser.username" type="text" class="form-control" :placeholder="$t('words.username')" disabled > </div> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> <label class="col-form-label required">{{ $t('words.firstname') }}</label> </div> <div class="col"> <ValidationProvider ref="first_name" rules="required" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <input data-test="userProfile-firstname" v-model="formUser.first_name" type="text" class="form-control" :placeholder="$t('words.firstname')" > <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> <label class="col-form-label required">{{ $t('words.lastname') }}</label> </div> <div class="col"> <ValidationProvider ref="last_name" rules="required" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <input data-test="userProfile-lastname" v-model="formUser.last_name" type="text" class="form-control" :placeholder="$t('words.lastname')" > <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> <label class="col-form-label"> {{ $t('words.email') }} </label> </div> <div class="col"> <ValidationProvider ref="email" rules="required|email" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <input data-test="userProfile-email" v-model="formUser.email" type="mail" class="form-control" :placeholder="$t('words.email')" disabled > <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> <label class="col-form-label">{{ $t('words.phone') }}</label> </div> <div class="col"> <ValidationProvider ref="phone_number" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <input data-test="userProfile-phone" v-model="formUser.phone_number" type="text" class="form-control" placeholder="" > <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> <label class="col-form-label"> {{ $t('profile.form.personalDetails.reason') }} </label> </div> <div class="col"> <textarea data-test="userProfile-comments" v-model="formUser.comments" class="form-control" /> </div> </div> <div class="row g-2 align-items-center" style="margin-bottom: 0.5em;"> <div class="col-3"> <label class="col-form-label" style="font-size: 0.87em;" > {{ $t('profile.form.personalDetails.organisation.label') }} </label> </div> <div v-if="userData" class="col"> <div v-if="userData.usergroup_roles.length === 0"> {{ $t('profile.form.personalDetails.organisation.noOrganisation') }} </div> <b-list-group v-else > <b-list-group-item v-for="usergroup of userData.usergroup_roles" :key="usergroup.usergroup.id" disabled > <b>{{ usergroup.usergroup.display_name }}</b> <em> {{ $t('as') }} </em> <b>{{ organisationsRoles.find(el => el.choice === usergroup.role).label }}</b> </b-list-group-item> </b-list-group> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> </div> <div v-if="userData && $config.client.mail" class="col"> <div style="margin: 0 0.5em 0.5em 0.1em; font-size: 0.9em; font-style: italic;" > {{ $t('profile.form.personalDetails.organisation.help') }} <a :href="`mailto:${$config.client.mail}`"> {{ $config.client.mail }} </a>. </div> </div> </div> </form> <div class="form-footer"> <b-button :disabled="(!formUser.first_name || !formUser.last_name || !formUser.email)" data-test="userProfile-submitUserInformations" @click.prevent="handleSubmit(submitUserInformations)" variant="primary" > {{ $t('words.validate') }} </b-button> </div> </b-overlay> </ValidationObserver> <ValidationObserver v-slot="{ handleSubmit }"> <b-overlay :show="loadingUserEmail" rounded="lg" variant="white" :style="'padding: 5px;'" > <form> <h5>{{ $t('profile.form.emailChange.title') }}</h5> <hr class="divider"> <div class="row g-2 align-items-center"> <div class="col-3" style="padding-right: 0;"> <label class="col-form-label required" style="font-size: 0.9em;" > {{ $t('profile.form.emailChange.label') }} </label> </div> <div class="col"> <ValidationProvider ref="email" rules="email" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <input data-test="userProfile-newEmail" v-model="formEmail.new_email" type="mail" class="form-control" :placeholder="$t('words.email')" > <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> </div> <div class="col"> <div style="margin: 0 0.5em 0.5em 0.5em; font-size: 0.8em; font-style: italic;" > {{ $t('profile.form.emailChange.help') }} </div> </div> </div> </form> <div class="form-footer"> <b-button :disabled="!formEmail.new_email" :pressed="btnPressed" data-test="userProfile-submitNewEmail" @click.prevent="handleSubmit(submitNewEmail)" variant="primary" > {{ $t('words.validate') }} </b-button> </div> </b-overlay> </ValidationObserver> <ValidationObserver v-slot="{ handleSubmit }"> <b-overlay :show="loadingUserPassword" rounded="lg" variant="white" :style="'padding: 5px;'" > <form> <h5>{{ $t('profile.form.passwordChange.title') }}</h5> <hr class="divider"> <div class="row g-2 align-items-center"> <div class="col-3" style="padding-right: 0;"> <label class="col-form-label required" style="font-size: 0.9em;"> {{ $t('profile.form.passwordChange.oldLabel') }} </label> </div> <div class="col"> <ValidationProvider ref="password" rules="required" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <div class="input-group flex-nowrap"> <input data-test="userProfile-password" v-model="formPassword.password" class="form-control" :type="showPassword ? 'text' : 'password'" :placeholder="$t('profile.form.passwordChange.oldLabel')" > <span class="input-group-text"> <b-icon data-test="userProfile-toggleShowPassword" :icon="showPassword?'eye-slash-fill':'eye-fill'" @click="showPassword = !showPassword" /> </span> </div> <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3" style="padding-right: 0;"> <label class="col-form-label required" style="font-size: 0.9em;">{{ $t('profile.form.passwordChange.newLabel') }}</label> </div> <div class="col"> <ValidationProvider ref="newPassword1" v-slot="{ classes, errors }" vid="confirmation"> <div class="control" :class="classes"> <div class="input-group flex-nowrap"> <input data-test="userProfile-newPassword1" v-model="formPassword.newPassword1" :type="showNewPassword2 ? 'text' : 'password'" class="form-control" :placeholder="$t('profile.form.passwordChange.newPlaceholder')" > <span class="input-group-text"> <b-icon data-test="userProfile-toggleShowNewPassword" :icon="showNewPassword2?'eye-slash-fill':'eye-fill'" @click="showNewPassword2 = !showNewPassword2" /> </span> </div> <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3" style="padding-right: 0;"> <label class="col-form-label" style="font-size: 0.9em;"></label> </div> <div class="col"> <ValidationProvider ref="newPassword2" rules="confirmed:confirmation" v-slot="{ classes, errors }"> <div class="control" :class="classes"> <div class="input-group flex-nowrap"> <input data-test="userProfile-newPassword2" v-model="formPassword.newPassword2" class="form-control" :type="showNewPassword2 ? 'text' : 'password'" :placeholder="$t('profile.form.passwordChange.confirmPlaceholder')" > <span class="input-group-text"> <b-icon data-test="userProfile-toggleShowNewPassword2" :icon="showNewPassword2?'eye-slash-fill':'eye-fill'" @click="showNewPassword2 = !showNewPassword2" /> </span> </div> <span class="form-errors">{{ errors[0] }}</span> </div> </ValidationProvider> </div> </div> <div class="row g-2 align-items-center"> <div class="col-3"> </div> <div class="col"> <div class="infos"> <ul> <li>{{ $t('profile.form.passwordChange.passwordHelp')[0] }}</li> <li>{{ $t('profile.form.passwordChange.passwordHelp')[1] }}</li> <li>{{ $t('profile.form.passwordChange.passwordHelp')[2] }}</li> <li>{{ $t('profile.form.passwordChange.passwordHelp')[3] }}</li> </ul> </div> </div> </div> </form> <div class="form-footer"> <b-button :disabled="(!formPassword.password || !formPassword.newPassword1 || !formPassword.newPassword2)" :pressed="btnPressed" data-test="userProfile-submitNewPassword" @click.prevent="handleSubmit(submitNewPassword)" variant="primary" > {{ $t('words.validate') }} </b-button> </div> </b-overlay> </ValidationObserver> <h5>{{ $t('profile.personalDataExport.title') }}</h5> <hr class="divider"> <PersonalDataExport /> </div> </div> <small class="footer"> <p> {{ $t('footer') }} <a href="https://www.neogeo.fr/" target="_blank" rel="noopener">Neogeo-Technologies</a> </p> </small> </div> </template> <script> import { mapState, mapMutations, mapActions } from 'vuex'; import i18n from '@/i18n'; import Swal from "sweetalert2"; import "sweetalert2/dist/sweetalert2.min.css"; import PersonalDataExport from '../components/PersonalDataExport.vue'; import { ValidationObserver, ValidationProvider, extend, configure, } from 'vee-validate'; import { required, email, confirmed } from 'vee-validate/dist/rules'; extend('required', { ...required, message: () => i18n.t('errors.required') }); extend('email', { ...email, message: () => i18n.t('errors.email'), }); extend('confirmed', { ...confirmed, message: () => i18n.t('errors.confirmPassword'), }); configure({ classes: { valid: 'is-valid', invalid: 'is-invalid', }, }); export default { name: 'UserProfile', components: { PersonalDataExport, ValidationObserver, ValidationProvider, }, data() { return { loadingUserInformation: false, loadingUserPassword: false, loadingUserEmail: false, formUser: { first_name: null, last_name: null, email: null, phone_number: null, comments: null, username: null }, formEmail: { new_email: null }, formPassword: { password: null, newPassword1: null, newPassword2: null }, isOrganisationSelected: false, organisation: null, btnPressed: false, showPassword: false, showNewPassword2: false, }; }, computed: { ...mapState('user', [ 'userData', 'userError', 'success' ]), ...mapState('organisations', ['organisationsRoles']), ...mapState('sign-in', [ 'next' ]), logoPath() { return require(process.env.VUE_APP_LOGO); } }, watch: { '$i18n.locale': function(newValue, oldValue) { if (newValue !== oldValue) { this.$refs.form.validate(); } }, userError(newValue) { if (newValue) { for (const [key, value] of Object.entries(newValue)) { this.$refs[key].applyResult({ errors: value, valid: false, failedRules: {} }); } } } }, created() { this.SET_NEXT(this.$route.query.next || process.env.VUE_APP_NEXT_DEFAULT); if (!this.userData) { this.loadingUserInformation = true; this.GET_USER_DETAIL() .then(() => { this.formUser = { ...this.formUser, ...this.userData }; this.loadingUserInformation = false; }); } if (this.organisationsRoles.length === 0) { this.GET_ORGANISATIONS_ROLES(); } }, methods: { ...mapMutations('sign-in', [ 'SET_NEXT' ]), ...mapActions('user', [ 'GET_USER_DETAIL', 'UPDATE_USER_DETAIL' ]), ...mapActions('organisations', [ 'GET_ORGANISATIONS_ROLES' ]), goBackToNext() { if ( this.$config.authorizedRedirections.some(reg => { return reg.test(decodeURIComponent(this.next)) }) ) { this.$router.push(this.$route.path); window.location.pathname = this.next; } else { this.$router.push({ name: 'NotFound' }); } }, submitUserInformations() { this.loadingUserInformation = true this.UPDATE_USER_DETAIL(this.formUser) .then(() => { this.GET_USER_DETAIL() .then(() => { this.formUser = { ...this.formUser, ...this.userData }; this.loadingUserInformation = false; }) .catch(() => { this.loadingUserInformation = false; }); }) .catch(() => { this.loadingUserInformation = false; }); }, submitNewEmail() { const data = { new_email: this.formEmail.new_email, }; this.loadingUserEmail = true; this.UPDATE_USER_DETAIL(data) .then(() => { this.loadingUserEmail = false; Swal.fire({ position: 'center', heightAuto: false, icon: 'success', text: `Un e-mail est envoyé à votre nouvelle adresse. Il contient un lien de validation sur lequel vous devez cliquer pour confirmer le changement. `, showConfirmButton: true, confirmButtonText: 'OK', confirmButtonColor: '#187CC6' }); }); }, submitNewPassword() { const data = { password: this.formPassword.password, new_password: this.formPassword.newPassword2 }; this.loadingUserPassword = true; this.UPDATE_USER_DETAIL(data) .then(() => { this.loadingUserPassword = false; if (this.success && this.success.length) { Swal.fire({ position: 'center', heightAuto: false, icon: 'success', text: `Votre mot de passe a bien été modifié. Vous allez être redirigé vers la page de connexion. `, showConfirmButton: true, confirmButtonText: 'OK', confirmButtonColor: '#187CC6' }).then((result) => { if (result.isConfirmed) { this.$router.push({ name: 'SignIn' }); } }); } }); } } } </script> <style lang="less" scoped> #back-button { font-size: 1.5em; position: -webkit-sticky; /* Safari */ position: sticky; top: 5%; left: 40px; align-self: flex-start; border: 2px solid #9BD0FF; border-radius: 8px; letter-spacing: 1px; } .signup-container { margin: -1rem auto auto; width: 800px; height: fit-content; .signup-form { margin: 5rem 1rem; h4.title { color: #373b3d; .sub-title { font-size: 75%; color: #6b7479; } } hr.solid { border-top: 2px solid #373b3d; } #export_data { margin-top: 24px; } h5 { margin-bottom: 20px; margin-top: 40px; color: #373b3d; } form { margin-top: 20px; h5 { margin-bottom: 20px; margin-top: 40px; color: #373b3d; } .row { margin-bottom: 1.6rem; } .input-group { span { cursor: pointer; border-bottom-left-radius: 0; border-top-left-radius: 0; border-left: none } } } .infos { font-size: 0.7em; font-style: italic; margin-right: 1em; ul { padding-left: 1rem; } } .form-footer { display: flex; justify-content: flex-end; margin-left: 7px; margin-top: 30px; button { margin-left: 2em; } button.btn-primary { border: 2px solid #9BD0FF; border-radius: 8px; } button.btn-outline-secondary { background-color: #F7F8FA; border: 2px solid #A9B2B9; border-radius: 8px; color: #2F3234; } button.btn-outline-secondary:hover { color: white; background-color: #4b4b4b; } } } } .form-errors { color: #EB0600 !important; } .form-success { color: #30C963 !important; } .footer { position: relative; bottom: 0; font-size: small; margin-top: 2rem; } .footer a { text-decoration: none; } </style>