mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +00:00
feat: Revamp hotkeys and change password in profile settings (#9311)
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:
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<form @submit.prevent="changePassword()">
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
<woot-input
|
||||
v-model="currentPassword"
|
||||
type="password"
|
||||
:styles="inputStyles"
|
||||
:class="{ error: $v.currentPassword.$error }"
|
||||
:label="$t('PROFILE_SETTINGS.FORM.CURRENT_PASSWORD.LABEL')"
|
||||
:placeholder="$t('PROFILE_SETTINGS.FORM.CURRENT_PASSWORD.PLACEHOLDER')"
|
||||
:error="`${
|
||||
$v.currentPassword.$error
|
||||
? $t('PROFILE_SETTINGS.FORM.CURRENT_PASSWORD.ERROR')
|
||||
: ''
|
||||
}`"
|
||||
@input="$v.currentPassword.$touch"
|
||||
/>
|
||||
|
||||
<woot-input
|
||||
v-model="password"
|
||||
type="password"
|
||||
:styles="inputStyles"
|
||||
:class="{ error: $v.password.$error }"
|
||||
:label="$t('PROFILE_SETTINGS.FORM.PASSWORD.LABEL')"
|
||||
:placeholder="$t('PROFILE_SETTINGS.FORM.PASSWORD.PLACEHOLDER')"
|
||||
:error="`${
|
||||
$v.password.$error ? $t('PROFILE_SETTINGS.FORM.PASSWORD.ERROR') : ''
|
||||
}`"
|
||||
@input="$v.password.$touch"
|
||||
/>
|
||||
|
||||
<woot-input
|
||||
v-model="passwordConfirmation"
|
||||
type="password"
|
||||
:styles="inputStyles"
|
||||
:class="{ error: $v.passwordConfirmation.$error }"
|
||||
:label="$t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.LABEL')"
|
||||
:placeholder="
|
||||
$t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.PLACEHOLDER')
|
||||
"
|
||||
:error="`${
|
||||
$v.passwordConfirmation.$error
|
||||
? $t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.ERROR')
|
||||
: ''
|
||||
}`"
|
||||
@input="$v.passwordConfirmation.$touch"
|
||||
/>
|
||||
|
||||
<form-button
|
||||
type="submit"
|
||||
color-scheme="primary"
|
||||
variant="solid"
|
||||
size="large"
|
||||
:disabled="isButtonDisabled"
|
||||
>
|
||||
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.BTN_TEXT') }}
|
||||
</form-button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { required, minLength } from 'vuelidate/lib/validators';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
import FormButton from 'v3/components/Form/Button.vue';
|
||||
export default {
|
||||
components: {
|
||||
FormButton,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
currentPassword: '',
|
||||
password: '',
|
||||
passwordConfirmation: '',
|
||||
isPasswordChanging: false,
|
||||
errorMessage: '',
|
||||
inputStyles: {
|
||||
borderRadius: '12px',
|
||||
padding: '6px 12px',
|
||||
fontSize: '14px',
|
||||
marginBottom: '2px',
|
||||
},
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
currentPassword: {
|
||||
required,
|
||||
},
|
||||
password: {
|
||||
minLength: minLength(6),
|
||||
},
|
||||
passwordConfirmation: {
|
||||
minLength: minLength(6),
|
||||
isEqPassword(value) {
|
||||
if (value !== this.password) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isButtonDisabled() {
|
||||
return (
|
||||
!this.currentPassword ||
|
||||
!this.passwordConfirmation ||
|
||||
!this.$v.passwordConfirmation.isEqPassword
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async changePassword() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.ERROR'));
|
||||
return;
|
||||
}
|
||||
let alertMessage = this.$t('PROFILE_SETTINGS.PASSWORD_UPDATE_SUCCESS');
|
||||
try {
|
||||
await this.$store.dispatch('updateProfile', {
|
||||
password: this.password,
|
||||
password_confirmation: this.passwordConfirmation,
|
||||
current_password: this.currentPassword,
|
||||
});
|
||||
} catch (error) {
|
||||
alertMessage =
|
||||
parseAPIErrorResponse(error) ||
|
||||
this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
|
||||
} finally {
|
||||
this.showAlert(alertMessage);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-4 w-full h-fit sm:max-h-[220px] p-4 sm:max-w-[350px] rounded-md border border-solid border-ash-200"
|
||||
:class="{
|
||||
'border-primary-300 ': active,
|
||||
}"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div class="flex flex-col gap-2 items-center w-full rounded-t-[5px]">
|
||||
<div class="flex items-center justify-between w-full gap-1">
|
||||
<div class="flex items-center text-base font-medium text-ash-900">
|
||||
{{ title }}
|
||||
</div>
|
||||
<input
|
||||
:checked="active"
|
||||
type="radio"
|
||||
:name="`hotkey-${title}`"
|
||||
class="shadow cursor-pointer grid place-items-center border-2 border-ash-200 appearance-none rounded-full w-5 h-5 checked:bg-primary-600 before:content-[''] before:bg-primary-600 before:border-4 before:rounded-full before:border-ash-25 checked:before:w-[18px] checked:before:h-[18px] checked:border checked:border-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-ash-900 text-sm line-clamp-2 leading-[1.4] text-start">
|
||||
{{ description }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<img
|
||||
:src="lightImage"
|
||||
:alt="`Light themed image for ${title}`"
|
||||
class="block object-cover w-full dark:hidden"
|
||||
/>
|
||||
<img
|
||||
:src="darkImage"
|
||||
:alt="`Dark themed image for ${title}`"
|
||||
class="hidden object-cover w-full dark:block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
lightImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
darkImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -33,6 +33,35 @@
|
||||
@update-signature="updateSignature"
|
||||
/>
|
||||
</form-section>
|
||||
<form-section
|
||||
:header="$t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.TITLE')"
|
||||
:description="$t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.NOTE')"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-between w-full gap-5 sm:gap-4 sm:flex-row"
|
||||
>
|
||||
<button
|
||||
v-for="hotKey in hotKeys"
|
||||
:key="hotKey.key"
|
||||
class="px-0 reset-base"
|
||||
>
|
||||
<hot-key-card
|
||||
:key="hotKey.title"
|
||||
:title="hotKey.title"
|
||||
:description="hotKey.description"
|
||||
:light-image="hotKey.lightImage"
|
||||
:dark-image="hotKey.darkImage"
|
||||
:active="isEditorHotKeyEnabled(uiSettings, hotKey.key)"
|
||||
@click="toggleHotKey(hotKey.key)"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</form-section>
|
||||
<form-section
|
||||
:header="$t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.TITLE')"
|
||||
>
|
||||
<change-password v-if="!globalConfig.disableUserProfileUpdate" />
|
||||
</form-section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,6 +78,8 @@ import { clearCookiesOnLogout } from 'dashboard/store/utils/api.js';
|
||||
import UserProfilePicture from './UserProfilePicture.vue';
|
||||
import UserBasicDetails from './UserBasicDetails.vue';
|
||||
import MessageSignature from './MessageSignature.vue';
|
||||
import HotKeyCard from './HotKeyCard.vue';
|
||||
import ChangePassword from './ChangePassword.vue';
|
||||
import FormSection from 'dashboard/components/FormSection.vue';
|
||||
|
||||
export default {
|
||||
@@ -57,6 +88,8 @@ export default {
|
||||
FormSection,
|
||||
UserProfilePicture,
|
||||
UserBasicDetails,
|
||||
HotKeyCard,
|
||||
ChangePassword,
|
||||
},
|
||||
mixins: [alertMixin, globalConfigMixin, uiSettingsMixin],
|
||||
data() {
|
||||
@@ -67,6 +100,31 @@ export default {
|
||||
displayName: '',
|
||||
email: '',
|
||||
messageSignature: '',
|
||||
hotKeys: [
|
||||
{
|
||||
key: 'enter',
|
||||
title: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.ENTER_KEY.HEADING'
|
||||
),
|
||||
description: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.ENTER_KEY.CONTENT'
|
||||
),
|
||||
lightImage: '/assets/images/dashboard/profile/hot-key-enter.svg',
|
||||
darkImage: '/assets/images/dashboard/profile/hot-key-enter-dark.svg',
|
||||
},
|
||||
{
|
||||
key: 'cmd_enter',
|
||||
title: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.CMD_ENTER_KEY.HEADING'
|
||||
),
|
||||
description: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.CMD_ENTER_KEY.CONTENT'
|
||||
),
|
||||
lightImage: '/assets/images/dashboard/profile/hot-key-ctrl-enter.svg',
|
||||
darkImage:
|
||||
'/assets/images/dashboard/profile/hot-key-ctrl-enter-dark.svg',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -156,6 +214,15 @@ export default {
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.AVATAR_DELETE_FAILED'));
|
||||
}
|
||||
},
|
||||
toggleHotKey(key) {
|
||||
this.hotKeys = this.hotKeys.map(hotKey =>
|
||||
hotKey.key === key ? { ...hotKey, active: !hotKey.active } : hotKey
|
||||
);
|
||||
this.updateUISettings({ editor_message_key: key });
|
||||
this.showAlert(
|
||||
this.$t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.UPDATE_SUCCESS')
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user