feat(voice): WhatsApp calling UI — inbox toggle, call widget, badges

Frontend for WhatsApp WABA calling: calling status toggle in inbox settings, WhatsApp Call channel tile, voice badges, call widget redesign, and conversation header call button.
This commit is contained in:
Tanmay Deep Sharma
2026-05-20 15:57:05 +05:30
parent 0ef7585ccb
commit a5ce1edf20
18 changed files with 350 additions and 94 deletions
@@ -19,6 +19,7 @@ import {
} from 'dashboard/helper/inbox';
import { useWhatsappCallSession } from 'dashboard/composables/useWhatsappCallSession';
import { useCallsStore } from 'dashboard/stores/calls';
import { useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { copyTextToClipboard } from 'shared/helpers/clipboard';
@@ -103,16 +104,32 @@ const hasSlaPolicyId = computed(() => props.chat?.sla_policy_id);
const callsStore = useCallsStore();
const whatsappCallSession = useWhatsappCallSession();
const contactsUiFlags = useMapGetter('contacts/getUIFlags');
const voiceCallProvider = computed(() => getVoiceCallProvider(inbox.value));
const isVoiceCallInbox = computed(() => voiceCallProvider.value !== null);
const isWhatsappVoiceInbox = computed(
() => getVoiceCallProvider(inbox.value) === VOICE_CALL_PROVIDERS.WHATSAPP
() => voiceCallProvider.value === VOICE_CALL_PROVIDERS.WHATSAPP
);
const isWhatsappCallButtonDisabled = computed(
() =>
whatsappCallSession.isInitiating.value ||
callsStore.hasActiveCall ||
callsStore.hasIncomingCall
const isCallButtonDisabled = computed(() => {
if (callsStore.hasActiveCall || callsStore.hasIncomingCall) return true;
if (isWhatsappVoiceInbox.value) {
return whatsappCallSession.isInitiating.value;
}
return contactsUiFlags.value?.isInitiatingCall || false;
});
const isCallButtonLoading = computed(() =>
isWhatsappVoiceInbox.value
? whatsappCallSession.isInitiating.value
: !!contactsUiFlags.value?.isInitiatingCall
);
const callButtonTooltip = computed(() =>
isWhatsappVoiceInbox.value
? t('CONVERSATION.HEADER.WHATSAPP_CALL')
: t('CONVERSATION.HEADER.VOICE_CALL')
);
const startWhatsappCall = async () => {
@@ -151,6 +168,31 @@ const startWhatsappCall = async () => {
}
};
const startTwilioCall = async () => {
if (contactsUiFlags.value?.isInitiatingCall) return;
try {
const response = await store.dispatch('contacts/initiateCall', {
contactId: currentContact.value.id,
inboxId: inbox.value?.id,
conversationId: currentChat.value.id,
});
callsStore.addCall({
callSid: response?.call_sid,
conversationId: response?.conversation_id ?? currentChat.value.id,
inboxId: inbox.value?.id,
callDirection: 'outbound',
});
} catch (error) {
useAlert(error?.message || t('CONVERSATION.HEADER.VOICE_CALL_FAILED'));
}
};
const startCall = () => {
if (isWhatsappVoiceInbox.value) return startWhatsappCall();
return startTwilioCall();
};
const copyConversationId = async () => {
try {
await copyTextToClipboard(String(props.chat.id));
@@ -230,16 +272,16 @@ const copyConversationId = async () => {
class="hidden md:flex"
/>
<ButtonV4
v-if="isWhatsappVoiceInbox"
v-tooltip.bottom="$t('CONVERSATION.HEADER.WHATSAPP_CALL')"
v-if="isVoiceCallInbox"
v-tooltip.bottom="callButtonTooltip"
size="sm"
variant="ghost"
color="slate"
icon="i-lucide-phone"
:is-loading="whatsappCallSession.isInitiating.value"
:disabled="isWhatsappCallButtonDisabled"
:is-loading="isCallButtonLoading"
:disabled="isCallButtonDisabled"
class="rounded-md hover:bg-n-alpha-2"
@click="startWhatsappCall"
@click="startCall"
/>
<MoreActions :conversation-id="currentChat.id" />
</div>