feat(v4): Compose new conversation without multiple clicks (#10545)
--------- Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { defineAsyncComponent, ref, computed } from 'vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
// import { useFileUpload } from 'dashboard/composables/useFileUpload';
|
||||
import { useFileUpload } from 'dashboard/composables/useFileUpload';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
@@ -110,19 +110,10 @@ const onClickInsertEmoji = emoji => {
|
||||
emit('insertEmoji', emoji);
|
||||
};
|
||||
|
||||
const useFileUpload = () => {
|
||||
// Empty function for testing purposes
|
||||
// TODO: Will use useFileUpload composable later
|
||||
return {
|
||||
onFileUpload: () => {},
|
||||
};
|
||||
};
|
||||
|
||||
const { onFileUpload } = useFileUpload({
|
||||
isATwilioSMSChannel: props.isTwilioSmsInbox,
|
||||
attachFile: ({ blob, file }) => {
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file.file);
|
||||
reader.onloadend = () => {
|
||||
|
||||
@@ -141,7 +141,11 @@ const isAnyDropdownActive = computed(() => {
|
||||
});
|
||||
|
||||
const handleContactSearch = value => {
|
||||
emit('searchContacts', value);
|
||||
showContactsDropdown.value = true;
|
||||
emit('searchContacts', {
|
||||
keys: ['email', 'phone_number', 'name'],
|
||||
query: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDropdownUpdate = (type, value) => {
|
||||
@@ -156,12 +160,12 @@ const handleDropdownUpdate = (type, value) => {
|
||||
|
||||
const searchCcEmails = value => {
|
||||
showCcEmailsDropdown.value = true;
|
||||
emit('searchContacts', value);
|
||||
emit('searchContacts', { keys: ['email'], query: value });
|
||||
};
|
||||
|
||||
const searchBccEmails = value => {
|
||||
showBccEmailsDropdown.value = true;
|
||||
emit('searchContacts', value);
|
||||
emit('searchContacts', { keys: ['email'], query: value });
|
||||
};
|
||||
|
||||
const setSelectedContact = async ({ value, action, ...rest }) => {
|
||||
@@ -250,7 +254,7 @@ const handleSendWhatsappMessage = async ({ message, templateParams }) => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="absolute right-0 w-[670px] mt-2 divide-y divide-n-strong overflow-visible transition-all duration-300 ease-in-out top-full justify-between flex flex-col bg-n-alpha-3 border border-n-strong shadow-sm backdrop-blur-[100px] rounded-xl"
|
||||
class="w-[670px] mt-2 divide-y divide-n-strong overflow-visible transition-all duration-300 ease-in-out top-full justify-between flex flex-col bg-n-alpha-3 border border-n-strong shadow-sm backdrop-blur-[100px] rounded-xl"
|
||||
>
|
||||
<ContactSelector
|
||||
:contacts="contacts"
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
|
||||
|
||||
import TagInput from 'dashboard/components-next/taginput/TagInput.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
const props = defineProps({
|
||||
contacts: {
|
||||
type: Array,
|
||||
@@ -43,20 +42,19 @@ const props = defineProps({
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'searchContacts',
|
||||
'setSelectedContact',
|
||||
'clearSelectedContact',
|
||||
'updateDropdown',
|
||||
]);
|
||||
|
||||
const i18nPrefix = 'COMPOSE_NEW_CONVERSATION.FORM.CONTACT_SELECTOR';
|
||||
const { t } = useI18n();
|
||||
|
||||
const contactsList = computed(() => {
|
||||
return props.contacts?.map(({ name, id, thumbnail, email, ...rest }) => ({
|
||||
id,
|
||||
label: `${name} (${email})`,
|
||||
label: email ? `${name} (${email})` : name,
|
||||
value: id,
|
||||
thumbnail: { name, src: thumbnail },
|
||||
...rest,
|
||||
@@ -69,13 +67,19 @@ const contactsList = computed(() => {
|
||||
const selectedContactLabel = computed(() => {
|
||||
return `${props.selectedContact?.name} (${props.selectedContact?.email})`;
|
||||
});
|
||||
|
||||
const errorClass = computed(() => {
|
||||
return props.hasErrors
|
||||
? '[&_input]:placeholder:!text-n-ruby-9 [&_input]:dark:placeholder:!text-n-ruby-9'
|
||||
: '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative flex-1 px-4 py-3 overflow-y-visible">
|
||||
<div class="flex items-baseline w-full gap-3 min-h-7">
|
||||
<label class="text-sm font-medium text-n-slate-11 whitespace-nowrap">
|
||||
{{ t('COMPOSE_NEW_CONVERSATION.FORM.CONTACT_SELECTOR.LABEL') }}
|
||||
{{ t(`${i18nPrefix}.LABEL`) }}
|
||||
</label>
|
||||
|
||||
<div
|
||||
@@ -83,9 +87,7 @@ const selectedContactLabel = computed(() => {
|
||||
class="flex items-center gap-1.5 rounded-md bg-n-alpha-2 px-3 min-h-7 min-w-0"
|
||||
>
|
||||
<span class="text-sm truncate text-n-slate-12">
|
||||
{{
|
||||
t('COMPOSE_NEW_CONVERSATION.FORM.CONTACT_SELECTOR.CONTACT_CREATING')
|
||||
}}
|
||||
{{ t(`${i18nPrefix}.CONTACT_CREATING`) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -95,9 +97,7 @@ const selectedContactLabel = computed(() => {
|
||||
<span class="text-sm truncate text-n-slate-12">
|
||||
{{
|
||||
isCreatingContact
|
||||
? t(
|
||||
'COMPOSE_NEW_CONVERSATION.FORM.CONTACT_SELECTOR.CONTACT_CREATING'
|
||||
)
|
||||
? t(`${i18nPrefix}.CONTACT_CREATING`)
|
||||
: selectedContactLabel
|
||||
}}
|
||||
</span>
|
||||
@@ -112,11 +112,7 @@ const selectedContactLabel = computed(() => {
|
||||
</div>
|
||||
<TagInput
|
||||
v-else
|
||||
:placeholder="
|
||||
t(
|
||||
'COMPOSE_NEW_CONVERSATION.FORM.CONTACT_SELECTOR.TAG_INPUT_PLACEHOLDER'
|
||||
)
|
||||
"
|
||||
:placeholder="t(`${i18nPrefix}.TAG_INPUT_PLACEHOLDER`)"
|
||||
mode="single"
|
||||
:menu-items="contactsList"
|
||||
:show-dropdown="showContactsDropdown"
|
||||
@@ -125,12 +121,8 @@ const selectedContactLabel = computed(() => {
|
||||
allow-create
|
||||
type="email"
|
||||
class="flex-1 min-h-7"
|
||||
:class="
|
||||
hasErrors
|
||||
? '[&_input]:placeholder:!text-n-ruby-9 [&_input]:dark:placeholder:!text-n-ruby-9'
|
||||
: ''
|
||||
"
|
||||
@focus="emit('updateDropdown', 'contacts', true)"
|
||||
:class="errorClass"
|
||||
focus-on-mount
|
||||
@input="emit('searchContacts', $event)"
|
||||
@on-click-outside="emit('updateDropdown', 'contacts', false)"
|
||||
@add="emit('setSelectedContact', $event)"
|
||||
|
||||
@@ -54,7 +54,7 @@ const targetInboxLabel = computed(() => {
|
||||
v-if="targetInbox"
|
||||
class="flex items-center gap-1.5 rounded-md bg-n-alpha-2 truncate px-3 h-7 min-w-0"
|
||||
>
|
||||
<span class="text-sm text-n-slate-12">
|
||||
<span class="text-sm truncate text-n-slate-12">
|
||||
{{ targetInboxLabel }}
|
||||
</span>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user