mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										31
									
								
								app/javascript/dashboard/components/FormSection.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/javascript/dashboard/components/FormSection.vue
									
									
									
									
									
										Normal 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>
 | 
				
			||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
    "PASSWORD_UPDATE_SUCCESS": "Your password has been changed successfully",
 | 
					    "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",
 | 
					    "AFTER_EMAIL_CHANGED": "Your profile has been updated successfully, please login again as your login credentials are changed",
 | 
				
			||||||
    "FORM": {
 | 
					    "FORM": {
 | 
				
			||||||
 | 
					      "PICTURE": "Profile Picture",
 | 
				
			||||||
      "AVATAR": "Profile Image",
 | 
					      "AVATAR": "Profile Image",
 | 
				
			||||||
      "ERROR": "Please fix form errors",
 | 
					      "ERROR": "Please fix form errors",
 | 
				
			||||||
      "REMOVE_IMAGE": "Remove",
 | 
					      "REMOVE_IMAGE": "Remove",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
@@ -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>
 | 
				
			||||||
@@ -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>
 | 
				
			||||||
@@ -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>
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -18,6 +18,7 @@ import reports from './reports/reports.routes';
 | 
				
			|||||||
import store from '../../../store';
 | 
					import store from '../../../store';
 | 
				
			||||||
import sla from './sla/sla.routes';
 | 
					import sla from './sla/sla.routes';
 | 
				
			||||||
import teams from './teams/teams.routes';
 | 
					import teams from './teams/teams.routes';
 | 
				
			||||||
 | 
					import personal from './personal/personal.routes';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  routes: [
 | 
					  routes: [
 | 
				
			||||||
@@ -50,5 +51,6 @@ export default {
 | 
				
			|||||||
    ...reports.routes,
 | 
					    ...reports.routes,
 | 
				
			||||||
    ...sla.routes,
 | 
					    ...sla.routes,
 | 
				
			||||||
    ...teams.routes,
 | 
					    ...teams.routes,
 | 
				
			||||||
 | 
					    ...personal.routes,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -273,5 +273,6 @@
 | 
				
			|||||||
  "chevrons-left-outline": ["m11 17-5-5 5-5", "m18 17-5-5 5-5"],
 | 
					  "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",
 | 
					  "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"],
 | 
					  "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"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user