diff --git a/app/builders/contact_inbox_builder.rb b/app/builders/contact_inbox_builder.rb index ffa45db2e..617161b53 100644 --- a/app/builders/contact_inbox_builder.rb +++ b/app/builders/contact_inbox_builder.rb @@ -21,6 +21,8 @@ class ContactInboxBuilder email_source_id when 'Channel::Sms' phone_source_id + when 'Channel::Voice' + phone_source_id # Voice uses phone number as source ID when 'Channel::Api', 'Channel::WebWidget' SecureRandom.uuid else @@ -35,7 +37,12 @@ class ContactInboxBuilder end def phone_source_id - raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number + unless @contact.phone_number.present? + # For voice channels, we'll create a fallback source ID if phone number is missing + return SecureRandom.uuid if @inbox.channel_type == 'Channel::Voice' + + raise ActionController::ParameterMissing, 'contact phone number' + end @contact.phone_number end @@ -98,6 +105,6 @@ class ContactInboxBuilder end def allowed_channels? - @inbox.email? || @inbox.sms? || @inbox.twilio? || @inbox.whatsapp? + @inbox.email? || @inbox.sms? || @inbox.twilio? || @inbox.whatsapp? || @inbox.channel_type == 'Channel::Voice' end end diff --git a/app/javascript/dashboard/components-next/CallModal/CallButton.vue b/app/javascript/dashboard/components-next/CallModal/CallButton.vue new file mode 100644 index 000000000..f8b10ccac --- /dev/null +++ b/app/javascript/dashboard/components-next/CallModal/CallButton.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/app/javascript/dashboard/components-next/CallModal/CallModal.vue b/app/javascript/dashboard/components-next/CallModal/CallModal.vue new file mode 100644 index 000000000..48142acd7 --- /dev/null +++ b/app/javascript/dashboard/components-next/CallModal/CallModal.vue @@ -0,0 +1,306 @@ + + + \ No newline at end of file diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue index a73de58b0..ad9696f7d 100644 --- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue +++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue @@ -8,6 +8,7 @@ import { useStore } from 'vuex'; import { useI18n } from 'vue-i18n'; import { useStorage } from '@vueuse/core'; import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts'; +import { INBOX_TYPES } from 'dashboard/helper/inbox'; import Button from 'dashboard/components-next/button/Button.vue'; import SidebarGroup from './SidebarGroup.vue'; @@ -16,6 +17,7 @@ import ChannelLeaf from './ChannelLeaf.vue'; import SidebarAccountSwitcher from './SidebarAccountSwitcher.vue'; import Logo from 'next/icon/Logo.vue'; import ComposeConversation from 'dashboard/components-next/NewConversation/ComposeConversation.vue'; +import CallButton from 'dashboard/components-next/CallModal/CallButton.vue'; const emit = defineEmits([ 'closeKeyShortcutModal', @@ -63,6 +65,11 @@ const conversationCustomViews = useMapGetter( 'customViews/getConversationCustomViews' ); +// Check if there are any voice inboxes +const hasVoiceInbox = computed(() => + inboxes.value.some(inbox => inbox.channel_type === INBOX_TYPES.VOICE) +); + onMounted(() => { store.dispatch('labels/get'); store.dispatch('inboxes/get'); @@ -510,6 +517,7 @@ const menuItems = computed(() => { {{ searchShortcut }} + + \ No newline at end of file diff --git a/app/javascript/dashboard/i18n/locale/en/callModal.json b/app/javascript/dashboard/i18n/locale/en/callModal.json new file mode 100644 index 000000000..9e6bdc212 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/en/callModal.json @@ -0,0 +1,20 @@ +{ + "CALL_MODAL": { + "START_CALL": "Start a call", + "MAKE_CALL_FROM": "Make the call from", + "TO": "To", + "VIA": "Via", + "SELECT_INBOX": "Select an inbox", + "ENTER_NUMBER_OR_NAME": "Enter a name or phone number...", + "CALL": "Call", + "CANCEL": "Cancel", + "CALL_DIRECTLY": "Call this number directly", + "SUCCESS_MESSAGE": "Call initiated successfully", + "ERROR_MESSAGE": "Failed to initiate call. Please try again.", + "CONTACT_SEARCH_ERROR": "Error searching contacts. Please try again.", + "VALIDATION_ERROR": "Please select both an inbox and a contact or phone number." + }, + "CALL_BUTTON": { + "TOOLTIP": "Make a call" + } +} \ No newline at end of file diff --git a/app/javascript/dashboard/i18n/locale/en/index.js b/app/javascript/dashboard/i18n/locale/en/index.js index 213387d0c..7fe58a9ac 100644 --- a/app/javascript/dashboard/i18n/locale/en/index.js +++ b/app/javascript/dashboard/i18n/locale/en/index.js @@ -5,6 +5,7 @@ import attributesMgmt from './attributesMgmt.json'; import auditLogs from './auditLogs.json'; import automation from './automation.json'; import bulkActions from './bulkActions.json'; +import callModal from './callModal.json'; import campaign from './campaign.json'; import cannedMgmt from './cannedMgmt.json'; import chatlist from './chatlist.json'; @@ -44,6 +45,7 @@ export default { ...auditLogs, ...automation, ...bulkActions, + ...callModal, ...campaign, ...cannedMgmt, ...chatlist, @@ -74,4 +76,4 @@ export default { ...sla, ...teamsSettings, ...whatsappTemplates, -}; +}; \ No newline at end of file diff --git a/app/javascript/dashboard/store/modules/contactConversations.js b/app/javascript/dashboard/store/modules/contactConversations.js index 55dfcf441..f5ed16ea6 100644 --- a/app/javascript/dashboard/store/modules/contactConversations.js +++ b/app/javascript/dashboard/store/modules/contactConversations.js @@ -2,6 +2,7 @@ import * as types from '../mutation-types'; import ContactAPI from '../../api/contacts'; import ConversationApi from '../../api/conversations'; import camelcaseKeys from 'camelcase-keys'; +import axios from 'axios'; export const createMessagePayload = (payload, message) => { const { content, cc_emails, bcc_emails } = message; @@ -82,12 +83,13 @@ export const getters = { }; export const actions = { - create: async ({ commit }, { params, isFromWhatsApp }) => { + create: async ({ commit }, { params, isFromWhatsApp, isVoiceCall }) => { commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true, }); const { contactId, files } = params; try { + // Create the basic payload const payload = setNewConversationPayload({ isFromWhatsApp, params, @@ -95,11 +97,35 @@ export const actions = { files, }); - const { data } = await ConversationApi.create(payload); - commit(types.default.ADD_CONTACT_CONVERSATION, { - id: contactId, - data, - }); + // If this is a voice call, adjust the endpoint to trigger voice + let data; + + if (isVoiceCall) { + const accountId = window.store.getters['accounts/getCurrentAccountId']; + + if (contactId) { + // Use the regular contacts call endpoint for existing contacts + const response = await axios.post(`/api/v1/accounts/${accountId}/contacts/${contactId}/call`); + data = response.data; + } else { + // For direct phone calls without a contact, use a special endpoint + // Add phoneNumber to the payload for voice call + payload.phone_number = params.phoneNumber || ''; + const response = await axios.post(`/api/v1/accounts/${accountId}/conversations/trigger_voice`, payload); + data = response.data; + } + } else { + // Regular conversation creation + const response = await ConversationApi.create(payload); + data = response.data; + } + + if (contactId) { + commit(types.default.ADD_CONTACT_CONVERSATION, { + id: contactId, + data, + }); + } return data; } catch (error) {