feat: Revamp basic profile, avatar and message signature (#9310)

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Muhsin Keloth
2024-04-29 16:41:55 +05:30
committed by GitHub
parent 28728635c9
commit c4eadd12ed
9 changed files with 442 additions and 1 deletions

View File

@@ -0,0 +1,31 @@
<template>
<div class="flex flex-col items-start w-full gap-6">
<div class="flex flex-col w-full gap-4">
<h4 v-if="title" class="text-lg font-medium text-ash-900">
{{ title }}
</h4>
<div class="flex flex-row items-center justify-between">
<div class="flex-grow h-px bg-ash-200" />
</div>
<p v-if="description" class="mb-0 text-sm font-normal text-ash-900">
{{ description }}
</p>
</div>
<div class="flex flex-col w-full gap-6">
<slot />
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
});
</script>

View File

@@ -10,6 +10,7 @@
"PASSWORD_UPDATE_SUCCESS": "Your password has been changed successfully",
"AFTER_EMAIL_CHANGED": "Your profile has been updated successfully, please login again as your login credentials are changed",
"FORM": {
"PICTURE": "Profile Picture",
"AVATAR": "Profile Image",
"ERROR": "Please fix form errors",
"REMOVE_IMAGE": "Remove",

View File

@@ -0,0 +1,161 @@
<template>
<div class="flex items-center w-full overflow-y-auto">
<div class="flex flex-col h-full p-5 pt-16 mx-auto my-0 font-inter">
<div class="flex flex-col gap-16 sm:max-w-[720px]">
<div class="flex flex-col gap-6">
<h2 class="mt-4 text-2xl font-medium text-ash-900">
{{ $t('PROFILE_SETTINGS.TITLE') }}
</h2>
<user-profile-picture
:src="avatarUrl"
:name="name"
size="72px"
@change="updateProfilePicture"
@delete="deleteProfilePicture"
/>
<user-basic-details
:name="name"
:display-name="displayName"
:email="email"
:email-enabled="!globalConfig.disableUserProfileUpdate"
@update-user="updateProfile"
/>
</div>
<form-section
:title="$t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.TITLE')"
:description="
$t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.NOTE')
"
>
<message-signature
:message-signature="messageSignature"
@update-signature="updateSignature"
/>
</form-section>
</div>
</div>
</div>
</template>
<script>
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import uiSettingsMixin, {
isEditorHotKeyEnabled,
} from 'dashboard/mixins/uiSettings';
import alertMixin from 'shared/mixins/alertMixin';
import { mapGetters } from 'vuex';
import { clearCookiesOnLogout } from 'dashboard/store/utils/api.js';
import UserProfilePicture from './UserProfilePicture.vue';
import UserBasicDetails from './UserBasicDetails.vue';
import MessageSignature from './MessageSignature.vue';
import FormSection from 'dashboard/components/FormSection.vue';
export default {
components: {
MessageSignature,
FormSection,
UserProfilePicture,
UserBasicDetails,
},
mixins: [alertMixin, globalConfigMixin, uiSettingsMixin],
data() {
return {
avatarFile: '',
avatarUrl: '',
name: '',
displayName: '',
email: '',
messageSignature: '',
};
},
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
currentUserId: 'getCurrentUserID',
globalConfig: 'globalConfig/get',
}),
},
mounted() {
if (this.currentUserId) {
this.initializeUser();
}
},
methods: {
initializeUser() {
this.name = this.currentUser.name;
this.email = this.currentUser.email;
this.avatarUrl = this.currentUser.avatar_url;
this.displayName = this.currentUser.display_name;
this.messageSignature = this.currentUser.message_signature;
},
isEditorHotKeyEnabled,
async dispatchUpdate(payload, successMessage, errorMessage) {
let alertMessage = '';
try {
await this.$store.dispatch('updateProfile', payload);
alertMessage = successMessage;
return true; // return the value so that the status can be known
} catch (error) {
alertMessage = error?.response?.data?.error
? error.response.data.error
: errorMessage;
return false; // return the value so that the status can be known
} finally {
this.showAlert(alertMessage);
}
},
async updateProfile(userAttributes) {
const { name, email, displayName } = userAttributes;
const hasEmailChanged = this.currentUser.email !== email;
this.name = name || this.name;
this.email = email || this.email;
this.displayName = displayName || this.displayName;
const updatePayload = {
name: this.name,
email: this.email,
displayName: this.displayName,
avatar: this.avatarFile,
};
const success = await this.dispatchUpdate(
updatePayload,
hasEmailChanged
? this.$t('PROFILE_SETTINGS.AFTER_EMAIL_CHANGED')
: this.$t('PROFILE_SETTINGS.UPDATE_SUCCESS'),
this.$t('RESET_PASSWORD.API.ERROR_MESSAGE')
);
if (hasEmailChanged && success) clearCookiesOnLogout();
},
async updateSignature(signature) {
const payload = { message_signature: signature };
let successMessage = this.$t(
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.API_SUCCESS'
);
let errorMessage = this.$t(
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.API_ERROR'
);
await this.dispatchUpdate(payload, successMessage, errorMessage);
},
updateProfilePicture({ file, url }) {
this.avatarFile = file;
this.avatarUrl = url;
},
async deleteProfilePicture() {
try {
await this.$store.dispatch('deleteAvatar');
this.avatarUrl = '';
this.avatarFile = '';
this.showAlert(this.$t('PROFILE_SETTINGS.AVATAR_DELETE_SUCCESS'));
} catch (error) {
this.showAlert(this.$t('PROFILE_SETTINGS.AVATAR_DELETE_FAILED'));
}
},
},
};
</script>

View File

@@ -0,0 +1,50 @@
<template>
<form class="flex flex-col gap-6" @submit.prevent="updateSignature()">
<woot-message-editor
id="message-signature-input"
v-model="signature"
class="message-editor h-[10rem] !px-3"
:is-format-mode="true"
:placeholder="$t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE.PLACEHOLDER')"
:enabled-menu-options="customEditorMenuList"
:enable-suggestions="false"
:show-image-resize-toolbar="true"
/>
<form-button
type="submit"
color-scheme="primary"
variant="solid"
size="large"
>
{{ $t('PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.BTN_TEXT') }}
</form-button>
</form>
</template>
<script setup>
import { ref, watch } from 'vue';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import { MESSAGE_SIGNATURE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
import FormButton from 'v3/components/Form/Button.vue';
const props = defineProps({
messageSignature: {
type: String,
default: '',
},
});
const customEditorMenuList = MESSAGE_SIGNATURE_EDITOR_MENU_OPTIONS;
const signature = ref(props.messageSignature);
const emit = defineEmits(['update-signature']);
watch(
() => props.messageSignature,
newValue => {
signature.value = newValue;
}
);
const updateSignature = () => {
emit('update-signature', signature.value);
};
</script>

View File

@@ -0,0 +1,135 @@
<template>
<form class="flex flex-col gap-4" @submit.prevent="updateUser('profile')">
<woot-input
v-model="userName"
:styles="inputStyles"
:class="{ error: $v.userName.$error }"
:label="$t('PROFILE_SETTINGS.FORM.NAME.LABEL')"
:placeholder="$t('PROFILE_SETTINGS.FORM.NAME.PLACEHOLDER')"
:error="`${
$v.userName.$error ? $t('PROFILE_SETTINGS.FORM.NAME.ERROR') : ''
}`"
@input="$v.userName.$touch"
/>
<woot-input
v-model="userDisplayName"
:styles="inputStyles"
:class="{ error: $v.userDisplayName.$error }"
:label="$t('PROFILE_SETTINGS.FORM.DISPLAY_NAME.LABEL')"
:placeholder="$t('PROFILE_SETTINGS.FORM.DISPLAY_NAME.PLACEHOLDER')"
:error="`${
$v.userDisplayName.$error
? $t('PROFILE_SETTINGS.FORM.DISPLAY_NAME.ERROR')
: ''
}`"
@input="$v.userDisplayName.$touch"
/>
<woot-input
v-if="emailEnabled"
v-model="userEmail"
:styles="inputStyles"
:class="{ error: $v.userEmail.$error }"
:label="$t('PROFILE_SETTINGS.FORM.EMAIL.LABEL')"
:placeholder="$t('PROFILE_SETTINGS.FORM.EMAIL.PLACEHOLDER')"
:error="`${
$v.userEmail.$error ? $t('PROFILE_SETTINGS.FORM.EMAIL.ERROR') : ''
}`"
@input="$v.userEmail.$touch"
/>
<form-button
type="submit"
color-scheme="primary"
variant="solid"
size="large"
>
{{ $t('PROFILE_SETTINGS.BTN_TEXT') }}
</form-button>
</form>
</template>
<script>
import FormButton from 'v3/components/Form/Button.vue';
import { required, minLength, email } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
FormButton,
},
mixins: [alertMixin],
props: {
name: {
type: String,
default: '',
},
email: {
type: String,
default: '',
},
displayName: {
type: String,
default: '',
},
emailEnabled: {
type: Boolean,
default: false,
},
},
data() {
return {
userName: this.name,
userDisplayName: this.displayName,
userEmail: this.email,
inputStyles: {
borderRadius: '12px',
padding: '6px 12px',
fontSize: '14px',
marginBottom: '2px',
},
};
},
validations: {
userName: {
required,
minLength: minLength(1),
},
userDisplayName: {},
userEmail: {
required,
email,
},
},
watch: {
name: {
handler(value) {
this.userName = value;
},
immediate: true,
},
displayName: {
handler(value) {
this.userDisplayName = value;
},
immediate: true,
},
email: {
handler(value) {
this.userEmail = value;
},
immediate: true,
},
},
methods: {
async updateUser() {
this.$v.$touch();
if (this.$v.$invalid) {
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.ERROR'));
return;
}
this.$emit('update-user', {
name: this.userName,
displayName: this.userDisplayName,
email: this.userEmail,
});
},
},
};
</script>

View File

@@ -0,0 +1,40 @@
<template>
<div class="flex flex-col gap-2">
<span class="text-sm font-medium text-ash-900">
{{ $t('PROFILE_SETTINGS.FORM.PICTURE') }}
</span>
<profile-avatar
:src="src"
:name="userNameWithoutEmoji"
@change="updateProfilePicture"
@delete="deleteProfilePicture"
/>
</div>
</template>
<script setup>
import { computed } from 'vue';
import ProfileAvatar from 'v3/components/Form/ProfileAvatar.vue';
import { removeEmoji } from 'shared/helpers/emoji';
const props = defineProps({
src: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
});
const emits = defineEmits(['change', 'delete']);
const userNameWithoutEmoji = computed(() => removeEmoji(props.name));
const updateProfilePicture = e => {
emits('change', e);
};
const deleteProfilePicture = () => {
emits('delete');
};
</script>

View File

@@ -0,0 +1,20 @@
import { frontendURL } from 'dashboard/helper/URLHelper';
const Index = () => import('./Index.vue');
export default {
routes: [
{
path: frontendURL('accounts/:accountId/personal'),
name: 'personal_settings',
roles: ['administrator', 'agent'],
component: Index,
props: {
headerTitle: 'PROFILE_SETTINGS.TITLE',
icon: 'edit',
showNewButton: false,
showSidemenuIcon: false,
},
},
],
};

View File

@@ -18,6 +18,7 @@ import reports from './reports/reports.routes';
import store from '../../../store';
import sla from './sla/sla.routes';
import teams from './teams/teams.routes';
import personal from './personal/personal.routes';
export default {
routes: [
@@ -50,5 +51,6 @@ export default {
...reports.routes,
...sla.routes,
...teams.routes,
...personal.routes,
],
};

View File

@@ -273,5 +273,6 @@
"chevrons-left-outline": ["m11 17-5-5 5-5", "m18 17-5-5 5-5"],
"chevron-left-single-outline": "m15 18-6-6 6-6",
"chevrons-right-outline": ["m6 17 5-5-5-5", "m13 17 5-5-5-5"],
"chevron-right-single-outline": "m9 18 6-6-6-6"
"chevron-right-single-outline": "m9 18 6-6-6-6",
"avatar-upload-outline": "M19.754 11a.75.75 0 0 1 .743.648l.007.102v7a3.25 3.25 0 0 1-3.065 3.246l-.185.005h-11a3.25 3.25 0 0 1-3.244-3.066l-.006-.184V11.75a.75.75 0 0 1 1.494-.102l.006.102v7a1.75 1.75 0 0 0 1.607 1.745l.143.006h11A1.75 1.75 0 0 0 19 18.894l.005-.143V11.75a.75.75 0 0 1 .75-.75ZM6.22 7.216l4.996-4.996a.75.75 0 0 1 .976-.073l.084.072l5.005 4.997a.75.75 0 0 1-.976 1.134l-.084-.073l-3.723-3.716l.001 11.694a.75.75 0 0 1-.648.743l-.102.007a.75.75 0 0 1-.743-.648L11 16.255V4.558L7.28 8.277a.75.75 0 0 1-.976.073l-.084-.073a.75.75 0 0 1-.073-.977l.073-.084l4.996-4.996L6.22 7.216Z"
}