-
+
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
@@ -228,6 +229,10 @@ export default {
type: String,
default: '',
},
+ newConversationModalActive: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
...mapGetters({
@@ -274,6 +279,9 @@ export default {
}
return ALLOWED_FILE_TYPES;
},
+ enableDragAndDrop() {
+ return !this.newConversationModalActive;
+ },
audioRecorderPlayStopIcon() {
switch (this.recordingAudioState) {
// playing paused recording stopped inactive destroyed
@@ -346,12 +354,4 @@ export default {
@apply dark:bg-slate-800 bg-slate-100;
}
}
-
-.modal-mask {
- @apply text-slate-600 dark:text-slate-200 bg-white_transparent dark:bg-black_transparent flex-col;
-}
-
-.icon {
- @apply text-[5rem];
-}
diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue
index dbe70c979..1e5364070 100644
--- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue
@@ -86,6 +86,7 @@
@@ -115,6 +116,7 @@
:toggle-audio-recorder="toggleAudioRecorder"
:toggle-emoji-picker="toggleEmojiPicker"
:message="message"
+ :new-conversation-modal-active="newConversationModalActive"
@selectWhatsappTemplate="openWhatsappTemplateModal"
@toggle-editor="toggleRichContentEditor"
@replace-text="replaceText"
@@ -241,6 +243,7 @@ export default {
showUserMentions: false,
showCannedMenu: false,
showVariablesMenu: false,
+ newConversationModalActive: false,
};
},
computed: {
@@ -567,11 +570,25 @@ export default {
500,
true
);
+
+ // A hacky fix to solve the drag and drop
+ // Is showing on top of new conversation modal drag and drop
+ // TODO need to find a better solution
+ bus.$on(
+ BUS_EVENTS.NEW_CONVERSATION_MODAL,
+ this.onNewConversationModalActive
+ );
},
destroyed() {
document.removeEventListener('paste', this.onPaste);
document.removeEventListener('keydown', this.handleKeyEvents);
},
+ beforeDestroy() {
+ bus.$off(
+ BUS_EVENTS.NEW_CONVERSATION_MODAL,
+ this.onNewConversationModalActive
+ );
+ },
methods: {
toggleRichContentEditor() {
this.updateUISettings({
@@ -1060,6 +1077,14 @@ export default {
this.bccEmails = bcc.join(', ');
this.toEmails = to.join(', ');
},
+ onNewConversationModalActive(isActive) {
+ // Issue is if the new conversation modal is open and we drag and drop the file
+ // then the file is not getting attached to the new conversation modal
+ // and it is getting attached to the current conversation reply box
+ // so to fix this we are removing the drag and drop event listener from the current conversation reply box
+ // When new conversation modal is open
+ this.newConversationModalActive = isActive;
+ },
},
};
diff --git a/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue b/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue
index e45b29c02..0e2a402e8 100644
--- a/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue
@@ -33,7 +33,7 @@
{{ $t('WHATSAPP_TEMPLATES.PARSER.GO_BACK_LABEL') }}
-
+
{{ $t('WHATSAPP_TEMPLATES.PARSER.SEND_MESSAGE_LABEL') }}
diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json
index 02f512722..7ad18b792 100644
--- a/app/javascript/dashboard/i18n/locale/en/contact.json
+++ b/app/javascript/dashboard/i18n/locale/en/contact.json
@@ -204,6 +204,10 @@
"PLACEHOLDER": "Write your message here",
"ERROR": "Message can't be empty"
},
+ "ATTACHMENTS": {
+ "SELECT": "Choose files",
+ "HELP_TEXT": "Drag and drop files here or choose files to attach"
+ },
"SUBMIT": "Send message",
"CANCEL": "Cancel",
"SUCCESS_MESSAGE": "Message sent!",
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue
index 08fc76798..7333d0664 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue
@@ -177,6 +177,7 @@ import alertMixin from 'shared/mixins/alertMixin';
import adminMixin from '../../../../mixins/isAdmin';
import { mapGetters } from 'vuex';
import { getCountryFlag } from 'dashboard/helper/flag';
+import { BUS_EVENTS } from 'shared/constants/busEvents';
import {
isAConversationRoute,
getConversationDashboardRoute,
@@ -268,6 +269,7 @@ export default {
},
toggleConversationModal() {
this.showConversationModal = !this.showConversationModal;
+ bus.$emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, this.showConversationModal);
},
toggleDeleteModal() {
this.showDeleteModal = !this.showDeleteModal;
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
index d1f03b083..4a5180570 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
@@ -11,7 +11,10 @@
-
+
+
+
+
+ {{ $t('NEW_CONVERSATION.FORM.ATTACHMENTS.SELECT') }}
+
+
+ {{ $t('NEW_CONVERSATION.FORM.ATTACHMENTS.HELP_TEXT') }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
+
+
+
@@ -190,6 +248,11 @@ import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
import { getInboxSource } from 'dashboard/helper/inbox';
import { required, requiredIf } from 'vuelidate/lib/validators';
+import inboxMixin from 'shared/mixins/inboxMixin';
+import FileUpload from 'vue-upload-component';
+import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
+import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
+import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
import {
appendSignature,
removeSignature,
@@ -204,9 +267,11 @@ export default {
CannedResponse,
WhatsappTemplates,
InboxDropdownItem,
+ FileUpload,
+ AttachmentPreview,
MessageSignatureMissingAlert,
},
- mixins: [alertMixin, uiSettingsMixin],
+ mixins: [alertMixin, uiSettingsMixin, inboxMixin, fileUploadMixin],
props: {
contact: {
type: Object,
@@ -228,6 +293,7 @@ export default {
ccEmails: '',
targetInbox: {},
whatsappTemplateSelected: false,
+ attachedFiles: [],
};
},
validations: {
@@ -246,6 +312,7 @@ export default {
uiFlags: 'contacts/getUIFlags',
conversationsUiFlags: 'contactConversations/getUIFlags',
currentUser: 'getCurrentUser',
+ globalConfig: 'globalConfig/get',
messageSignature: 'getMessageSignature',
}),
sendWithSignature() {
@@ -255,7 +322,7 @@ export default {
signatureToApply() {
return this.messageSignature;
},
- emailMessagePayload() {
+ newMessagePayload() {
const payload = {
inboxId: this.targetInbox.id,
sourceId: this.targetInbox.sourceId,
@@ -264,6 +331,12 @@ export default {
mailSubject: this.subject,
assigneeId: this.currentUser.id,
};
+
+ if (this.attachedFiles && this.attachedFiles.length) {
+ payload.files = [];
+ this.setAttachmentPayload(payload);
+ }
+
if (this.ccEmails) {
payload.message.cc_emails = this.ccEmails;
}
@@ -327,6 +400,15 @@ export default {
hasWhatsappTemplates() {
return !!this.selectedInbox.inbox?.message_templates;
},
+ hasAttachments() {
+ return this.attachedFiles.length;
+ },
+ inbox() {
+ return this.targetInbox;
+ },
+ allowedFileTypes() {
+ return ALLOWED_FILE_TYPES;
+ },
},
watch: {
message(value) {
@@ -358,6 +440,33 @@ export default {
}
}
},
+ setAttachmentPayload(payload) {
+ this.attachedFiles.forEach(attachment => {
+ if (this.globalConfig.directUploadsEnabled) {
+ payload.files.push(attachment.blobSignedId);
+ } else {
+ payload.files.push(attachment.resource.file);
+ }
+ });
+ },
+ attachFile({ blob, file }) {
+ const reader = new FileReader();
+ reader.readAsDataURL(file.file);
+ reader.onloadend = () => {
+ this.attachedFiles.push({
+ currentChatId: this.contact.id,
+ resource: blob || file,
+ isPrivate: this.isPrivate,
+ thumb: reader.result,
+ blobSignedId: blob ? blob.signed_id : undefined,
+ });
+ };
+ },
+ removeAttachment(itemIndex) {
+ this.attachedFiles = this.attachedFiles.filter(
+ (item, index) => itemIndex !== index
+ );
+ },
onCancel() {
this.$emit('cancel');
},
@@ -381,15 +490,19 @@ export default {
return payload;
},
onFormSubmit() {
+ const isFromWhatsApp = false;
this.$v.$touch();
if (this.$v.$invalid) {
return;
}
- this.createConversation(this.emailMessagePayload);
+ this.createConversation({
+ payload: this.newMessagePayload,
+ isFromWhatsApp,
+ });
},
- async createConversation(payload) {
+ async createConversation({ payload, isFromWhatsApp }) {
try {
- const data = await this.onSubmit(payload);
+ const data = await this.onSubmit(payload, isFromWhatsApp);
const action = {
type: 'link',
to: `/app/accounts/${data.account_id}/conversations/${data.id}`,
@@ -413,8 +526,9 @@ export default {
this.whatsappTemplateSelected = val;
},
async onSendWhatsAppReply(messagePayload) {
+ const isFromWhatsApp = true;
const payload = this.prepareWhatsAppMessagePayload(messagePayload);
- await this.createConversation(payload);
+ await this.createConversation({ payload, isFromWhatsApp });
},
inboxReadableIdentifier(inbox) {
return `${inbox.name} (${inbox.channel_type})`;
@@ -453,6 +567,17 @@ export default {
}
}
+.file-uploads {
+ @apply text-start;
+}
+.multiselect-wrap--small.has-multi-select-error {
+ ::v-deep {
+ .multiselect__tags {
+ @apply border-red-500;
+ }
+ }
+}
+
::v-deep {
.mention--box {
@apply left-0 m-auto right-0 top-auto h-fit;
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue
index 10fa74d3f..b7527c096 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue
@@ -49,11 +49,11 @@ export default {
onSuccess() {
this.$emit('cancel');
},
- async onSubmit(contactItem) {
- const data = await this.$store.dispatch(
- 'contactConversations/create',
- contactItem
- );
+ async onSubmit(params, isFromWhatsApp) {
+ const data = await this.$store.dispatch('contactConversations/create', {
+ params,
+ isFromWhatsApp,
+ });
return data;
},
},
diff --git a/app/javascript/dashboard/store/modules/contactConversations.js b/app/javascript/dashboard/store/modules/contactConversations.js
index 8ba70214b..266f20dcf 100644
--- a/app/javascript/dashboard/store/modules/contactConversations.js
+++ b/app/javascript/dashboard/store/modules/contactConversations.js
@@ -3,6 +3,64 @@ import * as types from '../mutation-types';
import ContactAPI from '../../api/contacts';
import ConversationApi from '../../api/conversations';
+export const createMessagePayload = (payload, message) => {
+ const { content, cc_emails, bcc_emails } = message;
+ payload.append('message[content]', content);
+ if (cc_emails) payload.append('message[cc_emails]', cc_emails);
+ if (bcc_emails) payload.append('message[bcc_emails]', bcc_emails);
+};
+
+export const createConversationPayload = ({ params, contactId, files }) => {
+ const { inboxId, message, sourceId, mailSubject, assigneeId } = params;
+ const payload = new FormData();
+
+ if (message) {
+ createMessagePayload(payload, message);
+ }
+
+ if (files && files.length > 0) {
+ files.forEach(file => payload.append('message[attachments][]', file));
+ }
+
+ payload.append('inbox_id', inboxId);
+ payload.append('contact_id', contactId);
+ payload.append('source_id', sourceId);
+ payload.append('additional_attributes[mail_subject]', mailSubject);
+ payload.append('assignee_id', assigneeId);
+
+ return payload;
+};
+
+export const createWhatsAppConversationPayload = ({ params }) => {
+ const { inboxId, message, contactId, sourceId, assigneeId } = params;
+
+ const payload = {
+ inbox_id: inboxId,
+ contact_id: contactId,
+ source_id: sourceId,
+ message,
+ assignee_id: assigneeId,
+ };
+
+ return payload;
+};
+
+const setNewConversationPayload = ({
+ isFromWhatsApp,
+ params,
+ contactId,
+ files,
+}) => {
+ if (isFromWhatsApp) {
+ return createWhatsAppConversationPayload({ params });
+ }
+ return createConversationPayload({
+ params,
+ contactId,
+ files,
+ });
+};
+
const state = {
records: {},
uiFlags: {
@@ -20,27 +78,25 @@ export const getters = {
};
export const actions = {
- create: async ({ commit }, params) => {
+ create: async ({ commit }, { params, isFromWhatsApp }) => {
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
isCreating: true,
});
- const { inboxId, message, contactId, sourceId, mailSubject, assigneeId } =
- params;
+ const { contactId, files } = params;
try {
- const { data } = await ConversationApi.create({
- inbox_id: inboxId,
- contact_id: contactId,
- source_id: sourceId,
- additional_attributes: {
- mail_subject: mailSubject,
- },
- message,
- assignee_id: assigneeId,
+ const payload = setNewConversationPayload({
+ isFromWhatsApp,
+ params,
+ contactId,
+ files,
});
+
+ const { data } = await ConversationApi.create(payload);
commit(types.default.ADD_CONTACT_CONVERSATION, {
id: contactId,
data,
});
+
return data;
} catch (error) {
throw new Error(error);
diff --git a/app/javascript/dashboard/store/modules/specs/contactConversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/contactConversations/actions.spec.js
index 0de32311b..9a36dd321 100644
--- a/app/javascript/dashboard/store/modules/specs/contactConversations/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/contactConversations/actions.spec.js
@@ -1,5 +1,10 @@
import axios from 'axios';
-import { actions } from '../../contactConversations';
+import {
+ actions,
+ createMessagePayload,
+ createConversationPayload,
+ createWhatsAppConversationPayload,
+} from '../../contactConversations';
import * as types from '../../../mutation-types';
import conversationList from './fixtures';
@@ -44,16 +49,84 @@ describe('#actions', () => {
await actions.create(
{ commit },
{
- inboxId: 1,
- message: { content: 'hi' },
- contactId: 4,
- sourceId: 5,
- mailSubject: 'Mail Subject',
+ params: {
+ inboxId: 1,
+ message: { content: 'hi' },
+ contactId: 4,
+ sourceId: 5,
+ mailSubject: 'Mail Subject',
+ assigneeId: 6,
+ files: [],
+ },
+ isFromWhatsApp: false,
+ }
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
+ [
+ types.default.ADD_CONTACT_CONVERSATION,
+ { id: 4, data: conversationList[0] },
+ ],
+ [
+ types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG,
+ { isCreating: false },
+ ],
+ ]);
+ });
+ it('sends correct actions with files if API is success', async () => {
+ axios.post.mockResolvedValue({ data: conversationList[0] });
+ await actions.create(
+ { commit },
+ {
+ params: {
+ inboxId: 1,
+ message: { content: 'hi' },
+ contactId: 4,
+ sourceId: 5,
+ mailSubject: 'Mail Subject',
+ assigneeId: 6,
+ files: ['file1.pdf', 'file2.jpg'],
+ },
+ isFromWhatsApp: false,
+ }
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
+ [
+ types.default.ADD_CONTACT_CONVERSATION,
+ { id: 4, data: conversationList[0] },
+ ],
+ [
+ types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG,
+ { isCreating: false },
+ ],
+ ]);
+ });
+ it('sends correct actions actions if API is success for whatsapp conversation', async () => {
+ axios.post.mockResolvedValue({ data: conversationList[0] });
+ await actions.create(
+ { commit },
+ {
+ params: {
+ inboxId: 1,
+ message: {
+ content: 'hi',
+ template_params: {
+ name: 'hello_world',
+ category: 'MARKETING',
+ language: 'en_US',
+ processed_params: {},
+ },
+ },
+ contactId: 4,
+ sourceId: 5,
+ assigneeId: 6,
+ },
+ isFromWhatsApp: true,
}
);
expect(commit.mock.calls).toEqual([
[types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
-
[
types.default.ADD_CONTACT_CONVERSATION,
{ id: 4, data: conversationList[0] },
@@ -66,16 +139,46 @@ describe('#actions', () => {
});
it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' });
-
await expect(
actions.create(
{ commit },
{
- inboxId: 1,
- message: { content: 'hi' },
- contactId: 4,
- sourceId: 5,
- mailSubject: 'Mail Subject',
+ params: {
+ inboxId: 1,
+ message: { content: 'hi' },
+ contactId: 4,
+ assigneeId: 6,
+ sourceId: 5,
+ mailSubject: 'Mail Subject',
+ },
+ isFromWhatsApp: false,
+ }
+ )
+ ).rejects.toThrow(Error);
+ expect(commit.mock.calls).toEqual([
+ [types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
+ [
+ types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG,
+ { isCreating: false },
+ ],
+ ]);
+ });
+ it('sends correct actions with files if API is error', async () => {
+ axios.post.mockRejectedValue({ message: 'Incorrect header' });
+ await expect(
+ actions.create(
+ { commit },
+ {
+ params: {
+ inboxId: 1,
+ message: { content: 'hi' },
+ contactId: 4,
+ sourceId: 5,
+ mailSubject: 'Mail Subject',
+ assigneeId: 6,
+ files: ['file1.pdf', 'file2.jpg'],
+ },
+ isFromWhatsApp: false,
}
)
).rejects.toThrow(Error);
@@ -89,3 +192,116 @@ describe('#actions', () => {
});
});
});
+
+describe('createMessagePayload', () => {
+ it('creates message payload with cc and bcc emails', () => {
+ const payload = new FormData();
+ const message = {
+ content: 'Test message content',
+ cc_emails: 'cc@example.com',
+ bcc_emails: 'bcc@example.com',
+ };
+
+ createMessagePayload(payload, message);
+
+ expect(payload.get('message[content]')).toBe(message.content);
+ expect(payload.get('message[cc_emails]')).toBe(message.cc_emails);
+ expect(payload.get('message[bcc_emails]')).toBe(message.bcc_emails);
+ });
+
+ it('creates message payload without cc and bcc emails', () => {
+ const payload = new FormData();
+ const message = {
+ content: 'Test message content',
+ };
+
+ createMessagePayload(payload, message);
+
+ expect(payload.get('message[content]')).toBe(message.content);
+ expect(payload.get('message[cc_emails]')).toBeNull();
+ expect(payload.get('message[bcc_emails]')).toBeNull();
+ });
+});
+
+describe('createConversationPayload', () => {
+ it('creates conversation payload with message and attachments', () => {
+ const options = {
+ params: {
+ inboxId: '1',
+ message: {
+ content: 'Test message content',
+ },
+ sourceId: '12',
+ mailSubject: 'Test Subject',
+ assigneeId: '123',
+ },
+ contactId: '23',
+ files: ['file1.pdf', 'file2.jpg'],
+ };
+
+ const payload = createConversationPayload(options);
+
+ expect(payload.get('message[content]')).toBe(
+ options.params.message.content
+ );
+ expect(payload.get('inbox_id')).toBe(options.params.inboxId);
+ expect(payload.get('contact_id')).toBe(options.contactId);
+ expect(payload.get('source_id')).toBe(options.params.sourceId);
+ expect(payload.get('additional_attributes[mail_subject]')).toBe(
+ options.params.mailSubject
+ );
+ expect(payload.get('assignee_id')).toBe(options.params.assigneeId);
+ expect(payload.getAll('message[attachments][]')).toEqual(options.files);
+ });
+
+ it('creates conversation payload with message and without attachments', () => {
+ const options = {
+ params: {
+ inboxId: '1',
+ message: {
+ content: 'Test message content',
+ },
+ sourceId: '12',
+ mailSubject: 'Test Subject',
+ assigneeId: '123',
+ },
+ contactId: '23',
+ };
+
+ const payload = createConversationPayload(options);
+
+ expect(payload.get('message[content]')).toBe(
+ options.params.message.content
+ );
+ expect(payload.get('inbox_id')).toBe(options.params.inboxId);
+ expect(payload.get('contact_id')).toBe(options.contactId);
+ expect(payload.get('source_id')).toBe(options.params.sourceId);
+ expect(payload.get('additional_attributes[mail_subject]')).toBe(
+ options.params.mailSubject
+ );
+ expect(payload.get('assignee_id')).toBe(options.params.assigneeId);
+ expect(payload.getAll('message[attachments][]')).toEqual([]);
+ });
+});
+
+describe('createWhatsAppConversationPayload', () => {
+ it('creates conversation payload with message', () => {
+ const options = {
+ params: {
+ inboxId: '1',
+ message: {
+ content: 'Test message content',
+ },
+ sourceId: '12',
+ assigneeId: '123',
+ },
+ };
+
+ const payload = createWhatsAppConversationPayload(options);
+
+ expect(payload.message).toBe(options.params.message);
+ expect(payload.inbox_id).toBe(options.params.inboxId);
+ expect(payload.source_id).toBe(options.params.sourceId);
+ expect(payload.assignee_id).toBe(options.params.assigneeId);
+ });
+});
diff --git a/app/javascript/shared/constants/busEvents.js b/app/javascript/shared/constants/busEvents.js
index 6c9a21072..6edf96fcd 100644
--- a/app/javascript/shared/constants/busEvents.js
+++ b/app/javascript/shared/constants/busEvents.js
@@ -10,4 +10,5 @@ export const BUS_EVENTS = {
ON_MESSAGE_LIST_SCROLL: 'ON_MESSAGE_LIST_SCROLL',
WEBSOCKET_DISCONNECT: 'WEBSOCKET_DISCONNECT',
SHOW_TOAST: 'newToastMessage',
+ NEW_CONVERSATION_MODAL: 'newConversationModal',
};