Feature: Agent Profile Update with avatar (#449)

* Feature: Agent Profile Update with avatar
* Add Update Profile with name, avatar, email and password
This commit is contained in:
Pranav Raj S
2020-02-16 17:20:38 +05:30
committed by GitHub
parent e61ba95cf7
commit c4e2a84f65
25 changed files with 584 additions and 133 deletions

View File

@@ -7,7 +7,7 @@
<span>{{ headerTitle }}</span>
</h1>
<router-link
v-if="showNewButton && showButton && currentRole"
v-if="showNewButton && showButton && isAdmin"
:to="buttonRoute"
class="button icon success nice button--fixed-right-top"
>
@@ -17,18 +17,30 @@
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import BackButton from '../../../components/widgets/BackButton';
import Auth from '../../../api/auth';
export default {
components: {
BackButton,
},
props: {
headerTitle: String,
buttonRoute: String,
buttonText: String,
icon: String,
headerTitle: {
default: '',
type: String,
},
buttonRoute: {
default: '',
type: String,
},
buttonText: {
default: '',
type: String,
},
icon: {
default: '',
type: String,
},
showButton: Boolean,
showNewButton: Boolean,
hideButtonRoutes: {
@@ -39,11 +51,14 @@ export default {
},
},
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
}),
iconClass() {
return `icon ${this.icon} header--icon`;
},
currentRole() {
const { role } = Auth.getCurrentUser();
isAdmin() {
const { role } = this.currentUser;
return role === 'administrator';
},
},

View File

@@ -108,7 +108,6 @@
/* global bus */
import { mapGetters } from 'vuex';
import md5 from 'md5';
import Thumbnail from '../../../../components/widgets/Thumbnail';
import AddAgent from './AddAgent';
@@ -182,10 +181,6 @@ export default {
agent => agent.role === 'administrator' && agent.confirmed
);
},
gravatarUrl(email) {
const hash = md5(email);
return `${window.WootConstants.GRAVATAR_URL}${hash}?default=404`;
},
// Edit Function
openAddPopup() {
this.showAddPopup = true;

View File

@@ -0,0 +1,224 @@
<template>
<div class="columns profile--settings ">
<form @submit.prevent="updateUser">
<div class="small-12 row profile--settings--row">
<div class="columns small-3 ">
<p class="section--title">
{{ $t('PROFILE_SETTINGS.FORM.PROFILE_SECTION.TITLE') }}
</p>
<p>{{ $t('PROFILE_SETTINGS.FORM.PROFILE_SECTION.NOTE') }}</p>
</div>
<div class="columns small-9">
<label>
{{ $t('PROFILE_SETTINGS.FORM.PROFILE_IMAGE.LABEL') }}
<thumbnail size="80px" :src="avatarUrl"></thumbnail>
<input
id="file"
ref="file"
type="file"
accept="image/*"
@change="handleImageUpload"
/>
</label>
<label :class="{ error: $v.name.$error }">
{{ $t('PROFILE_SETTINGS.FORM.NAME.LABEL') }}
<input
v-model="name"
type="text"
:placeholder="$t('PROFILE_SETTINGS.FORM.NAME.PLACEHOLDER')"
@input="$v.name.$touch"
/>
<span v-if="$v.name.$error" class="message">
{{ $t('PROFILE_SETTINGS.FORM.NAME.ERROR') }}
</span>
</label>
<label :class="{ error: $v.email.$error }">
{{ $t('PROFILE_SETTINGS.FORM.EMAIL.LABEL') }}
<input
v-model.trim="email"
type="email"
:placeholder="$t('PROFILE_SETTINGS.FORM.EMAIL.PLACEHOLDER')"
@input="$v.email.$touch"
/>
<span v-if="$v.email.$error" class="message">
{{ $t('PROFILE_SETTINGS.FORM.EMAIL.ERROR') }}
</span>
</label>
</div>
</div>
<div class="profile--settings--row row">
<div class="columns small-3 ">
<p class="section--title">
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.TITLE') }}
</p>
<p>{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.NOTE') }}</p>
</div>
<div class="columns small-9">
<label :class="{ error: $v.password.$error }">
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD.LABEL') }}
<input
v-model.trim="password"
type="password"
:placeholder="$t('PROFILE_SETTINGS.FORM.PASSWORD.PLACEHOLDER')"
@input="$v.password.$touch"
/>
<span v-if="$v.password.$error" class="message">
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD.ERROR') }}
</span>
</label>
<label :class="{ error: $v.passwordConfirmation.$error }">
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.LABEL') }}
<input
v-model.trim="passwordConfirmation"
type="password"
:placeholder="
$t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.PLACEHOLDER')
"
@input="$v.passwordConfirmation.$touch"
/>
<span v-if="$v.passwordConfirmation.$error" class="message">
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.ERROR') }}
</span>
</label>
</div>
</div>
<woot-submit-button
class="button nice success button--fixed-right-top"
:button-text="$t('PROFILE_SETTINGS.BTN_TEXT')"
:loading="isUpdating"
>
</woot-submit-button>
</form>
</div>
</template>
<script>
/* global bus */
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import { required, minLength, email } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import { clearCookiesOnLogout } from '../../../../api/auth';
export default {
components: {
Thumbnail,
},
data() {
return {
avatarFile: '',
avatarUrl: '',
name: '',
email: '',
password: '',
passwordConfirmation: '',
isUpdating: false,
};
},
validations: {
name: {
required,
},
email: {
required,
email,
},
password: {
minLength: minLength(6),
},
passwordConfirmation: {
minLength: minLength(6),
isEqPassword(value) {
if (value !== this.password) {
return false;
}
return true;
},
},
},
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
currentUserId: 'getCurrentUserID',
}),
},
watch: {
currentUserId(newCurrentUserId, prevCurrentUserId) {
if (prevCurrentUserId !== newCurrentUserId) {
this.initializeUser();
}
},
},
mounted() {
if (this.currentUserId) {
this.initializeUser();
}
},
methods: {
initializeUser() {
this.name = this.currentUser.name;
this.email = this.currentUser.email;
this.avatarUrl = this.currentUser.avatar_url;
},
async updateUser() {
this.$v.$touch();
if (this.$v.$invalid) {
bus.$emit('newToastMessage', this.$t('PROFILE_SETTINGS.FORM.ERROR'));
return;
}
this.isUpdating = true;
const hasEmailChanged = this.currentUser.email !== this.email;
try {
await this.$store.dispatch('updateProfile', {
name: this.name,
email: this.email,
avatar: this.avatarFile,
password: this.password,
password_confirmation: this.passwordConfirmation,
});
this.isUpdating = false;
if (hasEmailChanged) {
clearCookiesOnLogout();
bus.$emit(
'newToastMessage',
this.$t('PROFILE_SETTINGS.AFTER_EMAIL_CHANGED')
);
}
} catch (error) {
this.isUpdating = false;
}
},
handleImageUpload(event) {
const [file] = event.target.files;
this.avatarFile = file;
this.avatarUrl = URL.createObjectURL(file);
},
},
};
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/variables.scss';
@import '~dashboard/assets/scss/mixins.scss';
.profile--settings {
padding: 24px;
overflow: auto;
}
.profile--settings--row {
@include border-normal-bottom;
padding: 16px;
.small-3 {
padding: 16px 16px 16px 0;
}
.small-9 {
padding: 16px;
}
}
.section--title {
color: $color-woot;
}
</style>

View File

@@ -0,0 +1,27 @@
import SettingsContent from '../Wrapper';
import Index from './Index.vue';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: frontendURL('profile'),
name: 'profile_settings',
roles: ['administrator', 'agent'],
component: SettingsContent,
props: {
headerTitle: 'PROFILE_SETTINGS.TITLE',
icon: 'ion-compose',
showNewButton: false,
},
children: [
{
path: 'settings',
name: 'profile_settings_index',
component: Index,
roles: ['administrator', 'agent'],
},
],
},
],
};

View File

@@ -1,10 +1,11 @@
import agent from './agents/agent.routes';
import inbox from './inbox/inbox.routes';
import canned from './canned/canned.routes';
import reports from './reports/reports.routes';
import billing from './billing/billing.routes';
import Auth from '../../../api/auth';
import { frontendURL } from '../../../helper/URLHelper';
import agent from './agents/agent.routes';
import Auth from '../../../api/auth';
import billing from './billing/billing.routes';
import canned from './canned/canned.routes';
import inbox from './inbox/inbox.routes';
import profile from './profile/profile.routes';
import reports from './reports/reports.routes';
export default {
routes: [
@@ -19,10 +20,11 @@ export default {
return frontendURL('settings/canned-response');
},
},
...inbox.routes,
...agent.routes,
...canned.routes,
...reports.routes,
...billing.routes,
...canned.routes,
...inbox.routes,
...profile.routes,
...reports.routes,
],
};