mirror of
https://github.com/chatwoot/chatwoot.git
synced 2026-07-19 21:01:48 +08:00
270 lines
7.4 KiB
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>
|