chatwoot/app/javascript/dashboard/components-next/NewConversation/ComposeConversation.vue

270 lines
7.4 KiB
Vue

<script setup>
import { reactive, ref, computed, onMounted, watch } from 'vue';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'vue-i18n';
import { useUISettings } from 'dashboard/composables/useUISettings';
import { useAlert } from 'dashboard/composables';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
import { debounce } from '@chatwoot/utils';
import { emitter } from 'shared/helpers/mitt';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import {
createContactSearcher,
createNewContact,
fetchContactableInboxes,
processContactableInboxes,
mergeInboxDetails,
} from 'dashboard/components-next/NewConversation/helpers/composeConversationHelper';
import Popover from 'dashboard/components-next/popover/Popover.vue';
import ComposeNewConversationForm from 'dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue';
const props = defineProps({
contactId: {
type: String,
default: null,
},
align: {
type: String,
default: 'end',
},
});
const emit = defineEmits(['close']);
const searchContacts = createContactSearcher();
const store = useStore();
const { t } = useI18n();
const { fetchSignatureFlagFromUISettings } = useUISettings();
const popoverRef = ref(null);
const contacts = ref([]);
const selectedContact = ref(null);
const targetInbox = ref(null);
const isCreatingContact = ref(false);
const isFetchingInboxes = ref(false);
const isSearching = ref(false);
const formState = reactive({
message: '',
subject: '',
ccEmails: '',
bccEmails: '',
attachedFiles: [],
});
const clearFormState = () => {
Object.assign(formState, {
subject: '',
ccEmails: '',
bccEmails: '',
attachedFiles: [],
});
};
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 messageSignature = useMapGetter('getMessageSignature');
const inboxesList = useMapGetter('inboxes/getInboxes');
const sendWithSignature = computed(() =>
fetchSignatureFlagFromUISettings(targetInbox.value?.channelType)
);
const directUploadsEnabled = computed(
() => globalConfig.value.directUploadsEnabled
);
const activeContact = computed(() => contactById.value(props.contactId));
const onContactSearch = debounce(
async query => {
isSearching.value = true;
contacts.value = [];
try {
const results = await searchContacts(query);
// null means the request was aborted (a newer search is in-flight),
if (results === null) return;
contacts.value = results;
isSearching.value = false;
} catch (error) {
isSearching.value = false;
useAlert(t('COMPOSE_NEW_CONVERSATION.CONTACT_SEARCH.ERROR_MESSAGE'));
}
},
400,
false
);
const resetContacts = () => {
contacts.value = [];
};
const handleSelectedContact = async ({ value, action, ...rest }) => {
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;
contacts.value = [];
if (contact?.id) {
isFetchingInboxes.value = true;
try {
const contactableInboxes = await fetchContactableInboxes(contact.id);
// Merge the processed contactableInboxes with the inboxesList
selectedContact.value.contactInboxes = mergeInboxDetails(
contactableInboxes,
inboxesList.value
);
isFetchingInboxes.value = false;
} catch (error) {
isFetchingInboxes.value = false;
}
}
};
const handleTargetInbox = inbox => {
targetInbox.value = inbox;
if (!inbox) clearFormState();
resetContacts();
};
const clearSelectedContact = () => {
selectedContact.value = null;
targetInbox.value = null;
clearFormState();
};
const closeCompose = () => {
popoverRef.value?.hide();
if (!props.contactId) {
// If contactId is passed as prop
// Then don't allow to remove the selected contact
selectedContact.value = null;
}
targetInbox.value = null;
resetContacts();
};
const discardCompose = () => {
clearFormState();
formState.message = '';
closeCompose();
};
const createConversation = async ({ payload, isFromWhatsApp }) => {
try {
const data = await store.dispatch('contactConversations/create', {
params: payload,
isFromWhatsApp,
});
const action = {
type: 'link',
to: `/app/accounts/${data.account_id}/conversations/${data.id}`,
message: t('COMPOSE_NEW_CONVERSATION.FORM.GO_TO_CONVERSATION'),
};
discardCompose();
useAlert(t('COMPOSE_NEW_CONVERSATION.FORM.SUCCESS_MESSAGE'), action);
return true; // Return success
} catch (error) {
useAlert(
error instanceof ExceptionWithMessage
? error.data
: t('COMPOSE_NEW_CONVERSATION.FORM.ERROR_MESSAGE')
);
return false; // Return failure
}
};
const onPopoverShow = () => {
// Flag to prevent triggering drag n drop,
// When compose modal is active
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, true);
};
const onPopoverHide = () => {
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, false);
emit('close');
};
watch(
activeContact,
(currentContact, previousContact) => {
if (currentContact && props.contactId) {
// Reset on contact change
if (currentContact?.id !== previousContact?.id) {
clearSelectedContact();
clearFormState();
formState.message = '';
}
// First process the contactable inboxes to get the right structure
const processedInboxes = processContactableInboxes(
currentContact.contactInboxes || []
);
// Then Merge processedInboxes with the inboxes list
selectedContact.value = {
...currentContact,
contactInboxes: mergeInboxDetails(processedInboxes, inboxesList.value),
};
}
},
{ immediate: true, deep: true }
);
onMounted(() => resetContacts());
</script>
<template>
<Popover
ref="popoverRef"
:align="align"
:show-content-border="false"
@show="onPopoverShow"
@hide="onPopoverHide"
>
<template #default="{ isOpen }">
<slot name="trigger" :is-open="isOpen" />
</template>
<template #content>
<ComposeNewConversationForm
:form-state="formState"
:contacts="contacts"
:contact-id="contactId"
:is-loading="isSearching"
:current-user="currentUser"
:selected-contact="selectedContact"
:target-inbox="targetInbox"
:is-creating-contact="isCreatingContact"
:is-fetching-inboxes="isFetchingInboxes"
:is-direct-uploads-enabled="directUploadsEnabled"
:contact-conversations-ui-flags="uiFlags"
:contacts-ui-flags="contactsUiFlags"
:message-signature="messageSignature"
:send-with-signature="sendWithSignature"
@search-contacts="onContactSearch"
@reset-contact-search="resetContacts"
@update-selected-contact="handleSelectedContact"
@update-target-inbox="handleTargetInbox"
@clear-selected-contact="clearSelectedContact"
@create-conversation="createConversation"
@discard="discardCompose"
/>
</template>
</Popover>
</template>