chore: Minor fixes
This commit is contained in:
-67
@@ -1,67 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import { searchContacts } from 'dashboard/components-next/NewConversation/helpers/composeConversationHelper.js';
|
||||
|
||||
import ComposeNewConversation from 'dashboard/components-next/NewConversation/ComposeNewConversation.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const contacts = ref([]);
|
||||
const isSearching = ref(false);
|
||||
const showComposeNewConversation = ref(false);
|
||||
|
||||
const contactId = computed(() => route.params.contactId || null);
|
||||
|
||||
const onContactSearch = debounce(
|
||||
async query => {
|
||||
isSearching.value = true;
|
||||
contacts.value = [];
|
||||
try {
|
||||
contacts.value = await searchContacts(query);
|
||||
isSearching.value = false;
|
||||
} catch (error) {
|
||||
useAlert(t('COMPOSE_NEW_CONVERSATION.CONTACT_SEARCH.ERROR_MESSAGE'));
|
||||
} finally {
|
||||
isSearching.value = false;
|
||||
}
|
||||
},
|
||||
300,
|
||||
false
|
||||
);
|
||||
|
||||
const toggle = () => {
|
||||
showComposeNewConversation.value = !showComposeNewConversation.value;
|
||||
};
|
||||
|
||||
const closeCompose = () => {
|
||||
showComposeNewConversation.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onContactSearch('');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<slot
|
||||
name="trigger"
|
||||
:is-open="showComposeNewConversation"
|
||||
:toggle="toggle"
|
||||
/>
|
||||
|
||||
<ComposeNewConversation
|
||||
v-if="showComposeNewConversation"
|
||||
:contacts="contacts"
|
||||
:contact-id="contactId"
|
||||
:is-loading="isSearching"
|
||||
@search-contacts="onContactSearch"
|
||||
@discard="closeCompose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -9,7 +9,7 @@ import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
|
||||
import Breadcrumb from 'dashboard/components-next/breadcrumb/Breadcrumb.vue';
|
||||
import ContactActions from 'dashboard/components-next/Contacts/ContactHeader/ContactActions.vue';
|
||||
import ComposeConversation from 'dashboard/components-next/Contacts/ContactsForm/ComposeConversation.vue';
|
||||
import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue';
|
||||
|
||||
const props = defineProps({
|
||||
searchValue: {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import {
|
||||
searchContacts,
|
||||
createNewContact,
|
||||
fetchContactableInboxes,
|
||||
processContactableInboxes,
|
||||
} from 'dashboard/components-next/NewConversation/helpers/composeConversationHelper.js';
|
||||
|
||||
import ComposeNewConversationForm from 'dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const contacts = ref([]);
|
||||
const selectedContact = ref(null);
|
||||
const targetInbox = ref(null);
|
||||
|
||||
const isCreatingContact = ref(false);
|
||||
const isFetchingInboxes = ref(false);
|
||||
|
||||
const isSearching = ref(false);
|
||||
const showComposeNewConversation = ref(false);
|
||||
|
||||
const contactById = useMapGetter('contacts/getContactById');
|
||||
const contactId = computed(() => route.params.contactId || null);
|
||||
const activeContact = computed(() => contactById.value(contactId.value));
|
||||
|
||||
const onContactSearch = debounce(
|
||||
async query => {
|
||||
isSearching.value = true;
|
||||
contacts.value = [];
|
||||
try {
|
||||
contacts.value = await searchContacts(query);
|
||||
isSearching.value = false;
|
||||
} catch (error) {
|
||||
useAlert(t('COMPOSE_NEW_CONVERSATION.CONTACT_SEARCH.ERROR_MESSAGE'));
|
||||
} finally {
|
||||
isSearching.value = false;
|
||||
}
|
||||
},
|
||||
300,
|
||||
false
|
||||
);
|
||||
|
||||
const handleSelectedContact = async ({ value, action, ...rest }) => {
|
||||
try {
|
||||
let contact;
|
||||
|
||||
if (action === 'create') {
|
||||
isCreatingContact.value = true;
|
||||
try {
|
||||
contact = await createNewContact(value);
|
||||
isCreatingContact.value = false;
|
||||
} catch (error) {
|
||||
isCreatingContact.value = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
contact = rest;
|
||||
}
|
||||
selectedContact.value = contact;
|
||||
|
||||
if (contact?.id) {
|
||||
isFetchingInboxes.value = true;
|
||||
const contactableInboxes = await fetchContactableInboxes(contact.id);
|
||||
selectedContact.value.contactInboxes = contactableInboxes;
|
||||
isFetchingInboxes.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
isCreatingContact.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleTargetInbox = inbox => {
|
||||
targetInbox.value = inbox;
|
||||
};
|
||||
|
||||
const clearSelectedContact = () => {
|
||||
selectedContact.value = null;
|
||||
targetInbox.value = null;
|
||||
};
|
||||
|
||||
const closeCompose = () => {
|
||||
showComposeNewConversation.value = false;
|
||||
selectedContact.value = null;
|
||||
targetInbox.value = null;
|
||||
};
|
||||
|
||||
const toggle = () => {
|
||||
showComposeNewConversation.value = !showComposeNewConversation.value;
|
||||
};
|
||||
|
||||
watch(
|
||||
activeContact,
|
||||
() => {
|
||||
if (activeContact.value && contactId.value) {
|
||||
// Add null check for contactInboxes
|
||||
const contactInboxes = activeContact.value?.contactInboxes || [];
|
||||
selectedContact.value = {
|
||||
...activeContact.value,
|
||||
contactInboxes: processContactableInboxes(contactInboxes),
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
onContactSearch('');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<slot
|
||||
name="trigger"
|
||||
:is-open="showComposeNewConversation"
|
||||
:toggle="toggle"
|
||||
/>
|
||||
|
||||
<ComposeNewConversationForm
|
||||
v-if="showComposeNewConversation"
|
||||
:contacts="contacts"
|
||||
:contact-id="contactId"
|
||||
:is-loading="isSearching"
|
||||
:selected-contact="selectedContact"
|
||||
:target-inbox="targetInbox"
|
||||
:is-creating-contact="isCreatingContact"
|
||||
:is-fetching-inboxes="isFetchingInboxes"
|
||||
@search-contacts="onContactSearch"
|
||||
@update-selected-contact="handleSelectedContact"
|
||||
@update-target-inbox="handleTargetInbox"
|
||||
@clear-selected-contact="clearSelectedContact"
|
||||
@discard="closeCompose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
+59
-92
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { reactive, watch, ref, computed } from 'vue';
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
@@ -13,19 +13,16 @@ import {
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
import {
|
||||
buildContactableInboxesList,
|
||||
createNewContact,
|
||||
fetchContactableInboxes,
|
||||
prepareNewMessagePayload,
|
||||
prepareWhatsAppMessagePayload,
|
||||
processContactableInboxes,
|
||||
} from 'dashboard/components-next/NewConversation/helpers/composeConversationHelper.js';
|
||||
|
||||
import ContactSelector from './components/ContactSelector.vue';
|
||||
import InboxSelector from './components/InboxSelector.vue';
|
||||
import EmailOptions from './components/EmailOptions.vue';
|
||||
import MessageEditor from './components/MessageEditor.vue';
|
||||
import ActionButtons from './components/ActionButtons.vue';
|
||||
import InboxEmptyState from './components/InboxEmptyState.vue';
|
||||
import ContactSelector from './ContactSelector.vue';
|
||||
import InboxSelector from './InboxSelector.vue';
|
||||
import EmailOptions from './EmailOptions.vue';
|
||||
import MessageEditor from './MessageEditor.vue';
|
||||
import ActionButtons from './ActionButtons.vue';
|
||||
import InboxEmptyState from './InboxEmptyState.vue';
|
||||
|
||||
const props = defineProps({
|
||||
contacts: {
|
||||
@@ -36,13 +33,36 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
selectedContact: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
targetInbox: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
isCreatingContact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isFetchingInboxes: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['searchContacts', 'discard', 'success']);
|
||||
const emit = defineEmits([
|
||||
'searchContacts',
|
||||
'discard',
|
||||
'success',
|
||||
'updateSelectedContact',
|
||||
'updateTargetInbox',
|
||||
'clearSelectedContact',
|
||||
]);
|
||||
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
@@ -51,24 +71,17 @@ const showContactsDropdown = ref(false);
|
||||
const showInboxesDropdown = ref(false);
|
||||
const showCcEmailsDropdown = ref(false);
|
||||
const showBccEmailsDropdown = ref(false);
|
||||
const isCreatingContact = ref(false);
|
||||
const isFetchingInboxes = ref(false);
|
||||
|
||||
const contactById = useMapGetter('contacts/getContactById');
|
||||
const contactsUiFlags = useMapGetter('contacts/getUIFlags');
|
||||
const currentUser = useMapGetter('getCurrentUser');
|
||||
const globalConfig = useMapGetter('globalConfig/get');
|
||||
const uiFlags = useMapGetter('contactConversations/getUIFlags');
|
||||
const activeContact = computed(() => contactById.value(props.contactId));
|
||||
const directUploadsEnabled = computed(
|
||||
() => globalConfig.value.directUploadsEnabled
|
||||
);
|
||||
|
||||
const isCreating = computed(() => uiFlags.value.isCreating);
|
||||
|
||||
const selectedContact = ref(null);
|
||||
const targetInbox = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
message: '',
|
||||
subject: '',
|
||||
@@ -80,26 +93,26 @@ const state = reactive({
|
||||
const showBccInput = ref(false);
|
||||
|
||||
const inboxTypes = computed(() => ({
|
||||
isEmail: targetInbox.value?.channelType === INBOX_TYPES.EMAIL,
|
||||
isTwilio: targetInbox.value?.channelType === INBOX_TYPES.TWILIO,
|
||||
isWhatsapp: targetInbox.value?.channelType === INBOX_TYPES.WHATSAPP,
|
||||
isWebWidget: targetInbox.value?.channelType === INBOX_TYPES.WEB,
|
||||
isApi: targetInbox.value?.channelType === INBOX_TYPES.API,
|
||||
isEmail: props.targetInbox?.channelType === INBOX_TYPES.EMAIL,
|
||||
isTwilio: props.targetInbox?.channelType === INBOX_TYPES.TWILIO,
|
||||
isWhatsapp: props.targetInbox?.channelType === INBOX_TYPES.WHATSAPP,
|
||||
isWebWidget: props.targetInbox?.channelType === INBOX_TYPES.WEB,
|
||||
isApi: props.targetInbox?.channelType === INBOX_TYPES.API,
|
||||
isEmailOrWebWidget:
|
||||
targetInbox.value?.channelType === INBOX_TYPES.EMAIL ||
|
||||
targetInbox.value?.channelType === INBOX_TYPES.WEB,
|
||||
props.targetInbox?.channelType === INBOX_TYPES.EMAIL ||
|
||||
props.targetInbox?.channelType === INBOX_TYPES.WEB,
|
||||
isTwilioSMS:
|
||||
targetInbox.value?.channelType === INBOX_TYPES.TWILIO &&
|
||||
targetInbox.value?.medium === 'sms',
|
||||
props.targetInbox?.channelType === INBOX_TYPES.TWILIO &&
|
||||
props.targetInbox?.medium === 'sms',
|
||||
}));
|
||||
|
||||
const whatsappMessageTemplates = computed(() =>
|
||||
Object.keys(targetInbox.value?.messageTemplates || {}).length
|
||||
? targetInbox.value.messageTemplates
|
||||
Object.keys(props.targetInbox?.messageTemplates || {}).length
|
||||
? props.targetInbox.messageTemplates
|
||||
: []
|
||||
);
|
||||
|
||||
const inboxChannelType = computed(() => targetInbox.value?.channelType || '');
|
||||
const inboxChannelType = computed(() => props.targetInbox?.channelType || '');
|
||||
|
||||
const validationRules = computed(() => ({
|
||||
selectedContact: { required },
|
||||
@@ -109,8 +122,8 @@ const validationRules = computed(() => ({
|
||||
}));
|
||||
|
||||
const v$ = useVuelidate(validationRules, {
|
||||
selectedContact,
|
||||
targetInbox,
|
||||
selectedContact: computed(() => props.selectedContact),
|
||||
targetInbox: computed(() => props.targetInbox),
|
||||
message: computed(() => state.message),
|
||||
subject: computed(() => state.subject),
|
||||
});
|
||||
@@ -126,8 +139,8 @@ const validationStates = computed(() => ({
|
||||
const newMessagePayload = () => {
|
||||
const { message, subject, ccEmails, bccEmails, attachedFiles } = state;
|
||||
return prepareNewMessagePayload({
|
||||
targetInbox: targetInbox.value,
|
||||
selectedContact: selectedContact.value,
|
||||
targetInbox: props.targetInbox,
|
||||
selectedContact: props.selectedContact,
|
||||
message,
|
||||
subject,
|
||||
ccEmails,
|
||||
@@ -139,15 +152,15 @@ const newMessagePayload = () => {
|
||||
};
|
||||
|
||||
const contactableInboxesList = computed(() => {
|
||||
return buildContactableInboxesList(selectedContact.value?.contactInboxes);
|
||||
return buildContactableInboxesList(props.selectedContact?.contactInboxes);
|
||||
});
|
||||
|
||||
const showNoInboxAlert = computed(() => {
|
||||
return (
|
||||
selectedContact.value &&
|
||||
props.selectedContact &&
|
||||
contactableInboxesList.value.length === 0 &&
|
||||
!contactsUiFlags.value.isFetchingInboxes &&
|
||||
!isFetchingInboxes.value
|
||||
!props.isFetchingInboxes
|
||||
);
|
||||
});
|
||||
|
||||
@@ -180,56 +193,25 @@ const searchBccEmails = value => {
|
||||
};
|
||||
|
||||
const setSelectedContact = async ({ value, action, ...rest }) => {
|
||||
try {
|
||||
v$.value.$reset();
|
||||
let contact;
|
||||
|
||||
if (action === 'create') {
|
||||
isCreatingContact.value = true;
|
||||
try {
|
||||
contact = await createNewContact(value);
|
||||
isCreatingContact.value = false;
|
||||
} catch (error) {
|
||||
isCreatingContact.value = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
contact = rest;
|
||||
}
|
||||
selectedContact.value = contact;
|
||||
showContactsDropdown.value = false;
|
||||
|
||||
// Only proceed with fetching inboxes if we have a contact
|
||||
if (contact?.id) {
|
||||
isFetchingInboxes.value = true;
|
||||
const contactableInboxes = await fetchContactableInboxes(contact.id);
|
||||
selectedContact.value.contactInboxes = contactableInboxes;
|
||||
showInboxesDropdown.value = true;
|
||||
isFetchingInboxes.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
// Reset states in case of error
|
||||
isCreatingContact.value = false;
|
||||
showContactsDropdown.value = false;
|
||||
}
|
||||
v$.value.$reset();
|
||||
emit('updateSelectedContact', { value, action, ...rest });
|
||||
showContactsDropdown.value = false;
|
||||
showInboxesDropdown.value = true;
|
||||
};
|
||||
|
||||
const handleInboxAction = ({ value, action, ...rest }) => {
|
||||
v$.value.$reset();
|
||||
targetInbox.value = {
|
||||
...rest,
|
||||
};
|
||||
emit('updateTargetInbox', { ...rest });
|
||||
showInboxesDropdown.value = false;
|
||||
};
|
||||
|
||||
const removeTargetInbox = value => {
|
||||
v$.value.$reset();
|
||||
targetInbox.value = value;
|
||||
emit('updateTargetInbox', value);
|
||||
};
|
||||
|
||||
const clearSelectedContact = () => {
|
||||
selectedContact.value = null;
|
||||
targetInbox.value = null;
|
||||
emit('clearSelectedContact');
|
||||
};
|
||||
|
||||
const onClickInsertEmoji = emoji => {
|
||||
@@ -295,8 +277,8 @@ const handleSendMessage = async () => {
|
||||
|
||||
const handleSendWhatsappMessage = async ({ message, templateParams }) => {
|
||||
const whatsappMessagePayload = prepareWhatsAppMessagePayload({
|
||||
targetInbox: targetInbox.value,
|
||||
selectedContact: selectedContact.value,
|
||||
targetInbox: props.targetInbox,
|
||||
selectedContact: props.selectedContact,
|
||||
message,
|
||||
templateParams,
|
||||
currentUser: currentUser.value,
|
||||
@@ -307,21 +289,6 @@ const handleSendWhatsappMessage = async ({ message, templateParams }) => {
|
||||
});
|
||||
emit('discard');
|
||||
};
|
||||
|
||||
watch(
|
||||
activeContact,
|
||||
() => {
|
||||
if (activeContact.value && props.contactId) {
|
||||
selectedContact.value = {
|
||||
...activeContact.value,
|
||||
contactInboxes: processContactableInboxes(
|
||||
activeContact.value?.contactInboxes
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
+1
@@ -112,6 +112,7 @@ const selectedContactLabel = computed(() => {
|
||||
:show-dropdown="showContactsDropdown"
|
||||
:is-loading="isLoading"
|
||||
:disabled="contactableInboxesList?.length > 0 && showInboxesDropdown"
|
||||
allow-create
|
||||
type="email"
|
||||
class="flex-1 min-h-7"
|
||||
:class="
|
||||
|
||||
@@ -26,6 +26,7 @@ const props = defineProps({
|
||||
validator: value => ['single', 'multiple'].includes(value),
|
||||
},
|
||||
focusOnMount: { type: Boolean, default: false },
|
||||
allowCreate: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -99,6 +100,16 @@ const filteredMenuItems = computed(() => {
|
||||
return availableMenuItems;
|
||||
});
|
||||
|
||||
const emitDataOnAdd = emailValue => {
|
||||
const matchingMenuItem = props.menuItems.find(
|
||||
item => item.email === emailValue
|
||||
);
|
||||
|
||||
return matchingMenuItem
|
||||
? emit('add', { email: emailValue, ...matchingMenuItem })
|
||||
: emit('add', { value: emailValue, action: 'create' });
|
||||
};
|
||||
|
||||
const addTag = async () => {
|
||||
const trimmedTag = newTag.value.trim();
|
||||
if (!trimmedTag) return;
|
||||
@@ -108,7 +119,10 @@ const addTag = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.type === 'email' && !(await v$.value.$validate())) return;
|
||||
if (props.type === 'email' && props.allowCreate) {
|
||||
if (!(await v$.value.$validate())) return;
|
||||
emitDataOnAdd(trimmedTag);
|
||||
}
|
||||
|
||||
tags.value.push(trimmedTag);
|
||||
newTag.value = '';
|
||||
@@ -123,23 +137,18 @@ const removeTag = index => {
|
||||
};
|
||||
|
||||
const handleDropdownAction = async ({ email: emailAddress, ...rest }) => {
|
||||
if (!emailAddress) {
|
||||
emit('add', { email: emailAddress, ...rest });
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.mode === MODE.SINGLE && tags.value.length >= 1) return;
|
||||
|
||||
if (props.type === 'email') {
|
||||
if (props.type === 'email' && props.showDropdown) {
|
||||
newTag.value = emailAddress;
|
||||
if (!(await v$.value.$validate())) return;
|
||||
emit('add', { email: emailAddress, ...rest });
|
||||
}
|
||||
|
||||
tags.value.push(emailAddress);
|
||||
newTag.value = '';
|
||||
emit('update:modelValue', tags.value);
|
||||
tagInputRef.value?.focus();
|
||||
emit('add', { email: emailAddress, ...rest });
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
|
||||
Reference in New Issue
Block a user