mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 04:27:53 +00:00
feat: Add video call option with Dyte in the dashboard (#6207)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
23
app/javascript/dashboard/api/integrations/dyte.js
Normal file
23
app/javascript/dashboard/api/integrations/dyte.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* global axios */
|
||||||
|
|
||||||
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
|
class DyteAPI extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('integrations/dyte', { accountScoped: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
createAMeeting(conversationId) {
|
||||||
|
return axios.post(`${this.url}/create_a_meeting`, {
|
||||||
|
conversation_id: conversationId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addParticipantToMeeting(messageId) {
|
||||||
|
return axios.post(`${this.url}/add_participant_to_meeting`, {
|
||||||
|
message_id: messageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DyteAPI();
|
||||||
35
app/javascript/dashboard/api/specs/integrations/dyte.spec.js
Normal file
35
app/javascript/dashboard/api/specs/integrations/dyte.spec.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import DyteAPIClient from '../../integrations/dyte';
|
||||||
|
import ApiClient from '../../ApiClient';
|
||||||
|
import describeWithAPIMock from '../apiSpecHelper';
|
||||||
|
|
||||||
|
describe('#accountAPI', () => {
|
||||||
|
it('creates correct instance', () => {
|
||||||
|
expect(DyteAPIClient).toBeInstanceOf(ApiClient);
|
||||||
|
expect(DyteAPIClient).toHaveProperty('createAMeeting');
|
||||||
|
expect(DyteAPIClient).toHaveProperty('addParticipantToMeeting');
|
||||||
|
});
|
||||||
|
|
||||||
|
describeWithAPIMock('createAMeeting', context => {
|
||||||
|
it('creates a valid request', () => {
|
||||||
|
DyteAPIClient.createAMeeting(1);
|
||||||
|
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/integrations/dyte/create_a_meeting',
|
||||||
|
{
|
||||||
|
conversation_id: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describeWithAPIMock('addParticipantToMeeting', context => {
|
||||||
|
it('creates a valid request', () => {
|
||||||
|
DyteAPIClient.addParticipantToMeeting(1);
|
||||||
|
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/integrations/dyte/add_participant_to_meeting',
|
||||||
|
{
|
||||||
|
message_id: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<woot-button
|
||||||
|
v-if="isVideoIntegrationEnabled"
|
||||||
|
v-tooltip.top-end="
|
||||||
|
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
||||||
|
"
|
||||||
|
icon="video"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
color-scheme="secondary"
|
||||||
|
variant="smooth"
|
||||||
|
size="small"
|
||||||
|
@click="onClick"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [alertMixin],
|
||||||
|
props: {
|
||||||
|
conversationId: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { isLoading: false };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ appIntegrations: 'integrations/getAppIntegrations' }),
|
||||||
|
isVideoIntegrationEnabled() {
|
||||||
|
return this.appIntegrations.find(
|
||||||
|
integration => integration.id === 'dyte' && !!integration.hooks.length
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.appIntegrations.length) {
|
||||||
|
this.$store.dispatch('integrations/get');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onClick() {
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
await DyteAPI.createAMeeting(this.conversationId);
|
||||||
|
} catch (error) {
|
||||||
|
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.CREATE_ERROR'));
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -87,6 +87,10 @@
|
|||||||
:title="'Whatsapp Templates'"
|
:title="'Whatsapp Templates'"
|
||||||
@click="$emit('selectWhatsappTemplate')"
|
@click="$emit('selectWhatsappTemplate')"
|
||||||
/>
|
/>
|
||||||
|
<video-call-button
|
||||||
|
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
|
||||||
|
:conversation-id="conversationId"
|
||||||
|
/>
|
||||||
<transition name="modal-fade">
|
<transition name="modal-fade">
|
||||||
<div
|
<div
|
||||||
v-show="$refs.upload && $refs.upload.dropActive"
|
v-show="$refs.upload && $refs.upload.dropActive"
|
||||||
@@ -124,13 +128,13 @@ import {
|
|||||||
ALLOWED_FILE_TYPES,
|
ALLOWED_FILE_TYPES,
|
||||||
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
|
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
|
||||||
} from 'shared/constants/messages';
|
} from 'shared/constants/messages';
|
||||||
|
import VideoCallButton from '../VideoCallButton';
|
||||||
import { REPLY_EDITOR_MODES } from './constants';
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReplyBottomPanel',
|
name: 'ReplyBottomPanel',
|
||||||
components: { FileUpload },
|
components: { FileUpload, VideoCallButton },
|
||||||
mixins: [eventListenerMixins, uiSettingsMixin, inboxMixin],
|
mixins: [eventListenerMixins, uiSettingsMixin, inboxMixin],
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
@@ -209,6 +213,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
conversationId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
@@ -269,7 +277,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showMessageSignatureButton() {
|
showMessageSignatureButton() {
|
||||||
return !this.isPrivate && this.isAnEmailChannel;
|
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
||||||
},
|
},
|
||||||
sendWithSignature() {
|
sendWithSignature() {
|
||||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<li
|
<li v-if="shouldRenderMessage" :class="alignBubble">
|
||||||
v-if="hasAttachments || data.content || isEmailContentType"
|
|
||||||
:class="alignBubble"
|
|
||||||
>
|
|
||||||
<div :class="wrapClass">
|
<div :class="wrapClass">
|
||||||
<div v-tooltip.top-start="messageToolTip" :class="bubbleClass">
|
<div v-tooltip.top-start="messageToolTip" :class="bubbleClass">
|
||||||
<bubble-mail-head
|
<bubble-mail-head
|
||||||
@@ -17,6 +14,11 @@
|
|||||||
:is-email="isEmailContentType"
|
:is-email="isEmailContentType"
|
||||||
:display-quoted-button="displayQuotedButton"
|
:display-quoted-button="displayQuotedButton"
|
||||||
/>
|
/>
|
||||||
|
<bubble-integration
|
||||||
|
:message-id="data.id"
|
||||||
|
:content-attributes="contentAttributes"
|
||||||
|
:inbox-id="data.inbox_id"
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="isPending && hasAttachments"
|
v-if="isPending && hasAttachments"
|
||||||
class="chat-bubble has-attachment agent"
|
class="chat-bubble has-attachment agent"
|
||||||
@@ -111,14 +113,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
|
import BubbleActions from './bubble/Actions';
|
||||||
|
import BubbleFile from './bubble/File';
|
||||||
|
import BubbleImage from './bubble/Image';
|
||||||
|
import BubbleIntegration from './bubble/Integration.vue';
|
||||||
|
import BubbleLocation from './bubble/Location';
|
||||||
import BubbleMailHead from './bubble/MailHead';
|
import BubbleMailHead from './bubble/MailHead';
|
||||||
import BubbleText from './bubble/Text';
|
import BubbleText from './bubble/Text';
|
||||||
import BubbleImage from './bubble/Image';
|
|
||||||
import BubbleFile from './bubble/File';
|
|
||||||
import BubbleVideo from './bubble/Video.vue';
|
import BubbleVideo from './bubble/Video.vue';
|
||||||
import BubbleActions from './bubble/Actions';
|
|
||||||
import BubbleLocation from './bubble/Location';
|
|
||||||
|
|
||||||
import Spinner from 'shared/components/Spinner';
|
import Spinner from 'shared/components/Spinner';
|
||||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||||
|
|
||||||
@@ -130,12 +132,13 @@ import { generateBotMessageContent } from './helpers/botMessageContentHelper';
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BubbleActions,
|
BubbleActions,
|
||||||
BubbleText,
|
|
||||||
BubbleImage,
|
|
||||||
BubbleFile,
|
BubbleFile,
|
||||||
BubbleVideo,
|
BubbleImage,
|
||||||
BubbleMailHead,
|
BubbleIntegration,
|
||||||
BubbleLocation,
|
BubbleLocation,
|
||||||
|
BubbleMailHead,
|
||||||
|
BubbleText,
|
||||||
|
BubbleVideo,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Spinner,
|
Spinner,
|
||||||
},
|
},
|
||||||
@@ -169,6 +172,14 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
shouldRenderMessage() {
|
||||||
|
return (
|
||||||
|
this.hasAttachments ||
|
||||||
|
this.data.content ||
|
||||||
|
this.isEmailContentType ||
|
||||||
|
this.isAnIntegrationMessage
|
||||||
|
);
|
||||||
|
},
|
||||||
emailMessageContent() {
|
emailMessageContent() {
|
||||||
const {
|
const {
|
||||||
html_content: { full: fullHTMLContent } = {},
|
html_content: { full: fullHTMLContent } = {},
|
||||||
@@ -274,6 +285,9 @@ export default {
|
|||||||
isTemplate() {
|
isTemplate() {
|
||||||
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
|
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
|
||||||
},
|
},
|
||||||
|
isAnIntegrationMessage() {
|
||||||
|
return this.contentType === 'integrations';
|
||||||
|
},
|
||||||
emailHeadAttributes() {
|
emailHeadAttributes() {
|
||||||
return {
|
return {
|
||||||
email: this.contentAttributes.email,
|
email: this.contentAttributes.email,
|
||||||
|
|||||||
@@ -96,25 +96,26 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<reply-bottom-panel
|
<reply-bottom-panel
|
||||||
:mode="replyType"
|
:conversation-id="conversationId"
|
||||||
:inbox="inbox"
|
|
||||||
:send-button-text="replyButtonLabel"
|
|
||||||
:on-file-upload="onFileUpload"
|
|
||||||
:show-file-upload="showFileUpload"
|
|
||||||
:show-audio-recorder="showAudioRecorder"
|
|
||||||
:toggle-emoji-picker="toggleEmojiPicker"
|
|
||||||
:toggle-audio-recorder="toggleAudioRecorder"
|
|
||||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
|
||||||
:show-emoji-picker="showEmojiPicker"
|
|
||||||
:on-send="onSendReply"
|
|
||||||
:is-send-disabled="isReplyButtonDisabled"
|
|
||||||
:recording-audio-duration-text="recordingAudioDurationText"
|
|
||||||
:recording-audio-state="recordingAudioState"
|
|
||||||
:is-recording-audio="isRecordingAudio"
|
|
||||||
:is-on-private-note="isOnPrivateNote"
|
|
||||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
|
||||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||||
:has-whatsapp-templates="hasWhatsappTemplates"
|
:has-whatsapp-templates="hasWhatsappTemplates"
|
||||||
|
:inbox="inbox"
|
||||||
|
:is-on-private-note="isOnPrivateNote"
|
||||||
|
:is-recording-audio="isRecordingAudio"
|
||||||
|
:is-send-disabled="isReplyButtonDisabled"
|
||||||
|
:mode="replyType"
|
||||||
|
:on-file-upload="onFileUpload"
|
||||||
|
:on-send="onSendReply"
|
||||||
|
:recording-audio-duration-text="recordingAudioDurationText"
|
||||||
|
:recording-audio-state="recordingAudioState"
|
||||||
|
:send-button-text="replyButtonLabel"
|
||||||
|
:show-audio-recorder="showAudioRecorder"
|
||||||
|
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
||||||
|
:show-emoji-picker="showEmojiPicker"
|
||||||
|
:show-file-upload="showFileUpload"
|
||||||
|
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||||
|
:toggle-audio-recorder="toggleAudioRecorder"
|
||||||
|
:toggle-emoji-picker="toggleEmojiPicker"
|
||||||
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
||||||
@toggle-editor="toggleRichContentEditor"
|
@toggle-editor="toggleRichContentEditor"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<dyte-video-call
|
||||||
|
v-if="showDyteIntegration"
|
||||||
|
:message-id="messageId"
|
||||||
|
:meeting-data="contentAttributes.data"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import DyteVideoCall from './integrations/Dyte.vue';
|
||||||
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { DyteVideoCall },
|
||||||
|
mixins: [inboxMixin],
|
||||||
|
props: {
|
||||||
|
messageId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
contentAttributes: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
inboxId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showDyteIntegration() {
|
||||||
|
const isEnabledOnTheInbox = this.isAPIInbox || this.isAWebWidgetInbox;
|
||||||
|
return isEnabledOnTheInbox && this.contentAttributes.type === 'dyte';
|
||||||
|
},
|
||||||
|
inbox() {
|
||||||
|
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<woot-button
|
||||||
|
size="small"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
icon="video-add"
|
||||||
|
class="join-call-button"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
@click="joinTheCall"
|
||||||
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.DYTE.CLICK_HERE_TO_JOIN') }}
|
||||||
|
</woot-button>
|
||||||
|
<div v-if="dyteAuthToken" class="video-call--container">
|
||||||
|
<iframe
|
||||||
|
:src="meetingLink"
|
||||||
|
allow="camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
size="small"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
class="join-call-button"
|
||||||
|
@click="leaveTheRoom"
|
||||||
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.DYTE.LEAVE_THE_ROOM') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||||
|
|
||||||
|
const DYTE_MEETING_LINK = 'https://app.dyte.in/meeting/stage/';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [alertMixin],
|
||||||
|
props: {
|
||||||
|
messageId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
meetingData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { isLoading: false, dyteAuthToken: '', isSDKMounted: false };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
meetingLink() {
|
||||||
|
return `${DYTE_MEETING_LINK}${this.meetingData.room_name}?authToken=${this.dyteAuthToken}&showSetupScreen=true&disableVideoBackground=true`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async joinTheCall() {
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { authResponse: { authToken } = {} } = {},
|
||||||
|
} = await DyteAPI.addParticipantToMeeting(this.messageId);
|
||||||
|
this.dyteAuthToken = authToken;
|
||||||
|
} catch (err) {
|
||||||
|
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR'));
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leaveTheRoom() {
|
||||||
|
this.dyteAuthToken = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.join-call-button {
|
||||||
|
margin: var(--space-small) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-call--container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: var(--z-index-high);
|
||||||
|
padding: var(--space-smaller);
|
||||||
|
background: var(--b-800);
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--space-smaller);
|
||||||
|
right: 16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -35,10 +35,7 @@
|
|||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "There are no webhooks configured for this account.",
|
"404": "There are no webhooks configured for this account.",
|
||||||
"TITLE": "Manage webhooks",
|
"TITLE": "Manage webhooks",
|
||||||
"TABLE_HEADER": [
|
"TABLE_HEADER": ["Webhook endpoint", "Actions"]
|
||||||
"Webhook endpoint",
|
|
||||||
"Actions"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"EDIT": {
|
"EDIT": {
|
||||||
"BUTTON_TEXT": "Edit",
|
"BUTTON_TEXT": "Edit",
|
||||||
@@ -76,6 +73,13 @@
|
|||||||
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>"
|
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DYTE": {
|
||||||
|
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||||||
|
"LEAVE_THE_ROOM": "Leave the room",
|
||||||
|
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||||||
|
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||||||
|
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||||||
|
},
|
||||||
"DELETE": {
|
"DELETE": {
|
||||||
"BUTTON_TEXT": "Delete",
|
"BUTTON_TEXT": "Delete",
|
||||||
"API": {
|
"API": {
|
||||||
@@ -93,10 +97,7 @@
|
|||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "There are no dashboard apps configured on this account yet",
|
"404": "There are no dashboard apps configured on this account yet",
|
||||||
"LOADING": "Fetching dashboard apps...",
|
"LOADING": "Fetching dashboard apps...",
|
||||||
"TABLE_HEADER": [
|
"TABLE_HEADER": ["Name", "Endpoint"],
|
||||||
"Name",
|
|
||||||
"Endpoint"
|
|
||||||
],
|
|
||||||
"EDIT_TOOLTIP": "Edit app",
|
"EDIT_TOOLTIP": "Edit app",
|
||||||
"DELETE_TOOLTIP": "Delete app"
|
"DELETE_TOOLTIP": "Delete app"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,14 +16,15 @@ const state = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isAValidAppIntegration = integration => {
|
||||||
|
return ['dialogflow', 'dyte'].includes(integration.id);
|
||||||
|
};
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getIntegrations($state) {
|
getIntegrations($state) {
|
||||||
return $state.records.filter(
|
return $state.records.filter(item => !isAValidAppIntegration(item));
|
||||||
item => item.id !== 'fullcontact' && item.id !== 'dialogflow'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getAppIntegrations($state) {
|
getAppIntegrations($state) {
|
||||||
return $state.records.filter(item => item.id === 'dialogflow');
|
return $state.records.filter(item => isAValidAppIntegration(item));
|
||||||
},
|
},
|
||||||
getIntegration: $state => integrationId => {
|
getIntegration: $state => integrationId => {
|
||||||
const [integration] = $state.records.filter(
|
const [integration] = $state.records.filter(
|
||||||
|
|||||||
@@ -5,28 +5,40 @@ describe('#getters', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
records: [
|
records: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 'test1',
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
logo: 'test',
|
logo: 'test',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 'test2',
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
logo: 'test',
|
logo: 'test',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'dyte',
|
||||||
|
name: 'dyte',
|
||||||
|
logo: 'test',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dialogflow',
|
||||||
|
name: 'dialogflow',
|
||||||
|
logo: 'test',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(getters.getIntegrations(state)).toEqual([
|
expect(getters.getIntegrations(state)).toEqual([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 'test1',
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
logo: 'test',
|
logo: 'test',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 'test2',
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
logo: 'test',
|
logo: 'test',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -38,11 +50,17 @@ describe('#getters', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
records: [
|
records: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 'test1',
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
logo: 'test',
|
logo: 'test',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'dyte',
|
||||||
|
name: 'dyte',
|
||||||
|
logo: 'test',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'dialogflow',
|
id: 'dialogflow',
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
@@ -52,6 +70,12 @@ describe('#getters', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(getters.getAppIntegrations(state)).toEqual([
|
expect(getters.getAppIntegrations(state)).toEqual([
|
||||||
|
{
|
||||||
|
id: 'dyte',
|
||||||
|
name: 'dyte',
|
||||||
|
logo: 'test',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'dialogflow',
|
id: 'dialogflow',
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
|
|||||||
@@ -145,6 +145,7 @@
|
|||||||
"tag-outline": "M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465Zm0 1.5h-5.465c-.465 0-.91.185-1.239.513l-8.512 8.523a1.75 1.75 0 0 0 .015 2.462l4.461 4.454a1.755 1.755 0 0 0 2.477 0l8.5-8.503a1.75 1.75 0 0 0 .513-1.237V4.25a.75.75 0 0 0-.75-.75ZM17 5.502a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z",
|
"tag-outline": "M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465Zm0 1.5h-5.465c-.465 0-.91.185-1.239.513l-8.512 8.523a1.75 1.75 0 0 0 .015 2.462l4.461 4.454a1.755 1.755 0 0 0 2.477 0l8.5-8.503a1.75 1.75 0 0 0 .513-1.237V4.25a.75.75 0 0 0-.75-.75ZM17 5.502a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z",
|
||||||
"upload-outline": "M6.087 7.75a5.752 5.752 0 0 1 11.326 0h.087a4 4 0 0 1 3.962 4.552 6.534 6.534 0 0 0-1.597-1.364A2.501 2.501 0 0 0 17.5 9.25h-.756a.75.75 0 0 1-.75-.713 4.25 4.25 0 0 0-8.489 0 .75.75 0 0 1-.749.713H6a2.5 2.5 0 0 0 0 5h4.4a6.458 6.458 0 0 0-.357 1.5H6a4 4 0 0 1 0-8h.087ZM22 16.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-6-1.793V19.5a.5.5 0 0 0 1 0v-4.793l1.646 1.647a.5.5 0 0 0 .708-.708l-2.5-2.5a.5.5 0 0 0-.708 0l-2.5 2.5a.5.5 0 0 0 .708.708L16 14.707Z",
|
"upload-outline": "M6.087 7.75a5.752 5.752 0 0 1 11.326 0h.087a4 4 0 0 1 3.962 4.552 6.534 6.534 0 0 0-1.597-1.364A2.501 2.501 0 0 0 17.5 9.25h-.756a.75.75 0 0 1-.75-.713 4.25 4.25 0 0 0-8.489 0 .75.75 0 0 1-.749.713H6a2.5 2.5 0 0 0 0 5h4.4a6.458 6.458 0 0 0-.357 1.5H6a4 4 0 0 1 0-8h.087ZM22 16.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-6-1.793V19.5a.5.5 0 0 0 1 0v-4.793l1.646 1.647a.5.5 0 0 0 .708-.708l-2.5-2.5a.5.5 0 0 0-.708 0l-2.5 2.5a.5.5 0 0 0 .708.708L16 14.707Z",
|
||||||
"video-outline": "M13.75 4.5A3.25 3.25 0 0 1 17 7.75v.173l3.864-2.318A.75.75 0 0 1 22 6.248V17.75a.75.75 0 0 1-1.136.643L17 16.075v.175a3.25 3.25 0 0 1-3.25 3.25h-8.5A3.25 3.25 0 0 1 2 16.25v-8.5A3.25 3.25 0 0 1 5.25 4.5h8.5Zm0 1.5h-8.5A1.75 1.75 0 0 0 3.5 7.75v8.5c0 .966.784 1.75 1.75 1.75h8.5a1.75 1.75 0 0 0 1.75-1.75v-8.5A1.75 1.75 0 0 0 13.75 6Zm6.75 1.573L17 9.674v4.651l3.5 2.1V7.573Z",
|
"video-outline": "M13.75 4.5A3.25 3.25 0 0 1 17 7.75v.173l3.864-2.318A.75.75 0 0 1 22 6.248V17.75a.75.75 0 0 1-1.136.643L17 16.075v.175a3.25 3.25 0 0 1-3.25 3.25h-8.5A3.25 3.25 0 0 1 2 16.25v-8.5A3.25 3.25 0 0 1 5.25 4.5h8.5Zm0 1.5h-8.5A1.75 1.75 0 0 0 3.5 7.75v8.5c0 .966.784 1.75 1.75 1.75h8.5a1.75 1.75 0 0 0 1.75-1.75v-8.5A1.75 1.75 0 0 0 13.75 6Zm6.75 1.573L17 9.674v4.651l3.5 2.1V7.573Z",
|
||||||
|
"video-add-outline": "M13.75 4.5A3.25 3.25 0 0 1 17 7.75v.173l3.864-2.318A.75.75 0 0 1 22 6.248V17.75a.75.75 0 0 1-1.136.643L17 16.075v.175a3.25 3.25 0 0 1-3.25 3.25h-1.063c.154-.478.255-.98.294-1.5h.769a1.75 1.75 0 0 0 1.75-1.75v-8.5A1.75 1.75 0 0 0 13.75 6h-8.5A1.75 1.75 0 0 0 3.5 7.75v3.982A6.517 6.517 0 0 0 2 12.81V7.75A3.25 3.25 0 0 1 5.25 4.5h8.5Zm6.75 3.073L17 9.674v4.651l3.5 2.1V7.573ZM12 17.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0ZM7 18l.001 2.503a.5.5 0 1 1-1 0V18H3.496a.5.5 0 0 1 0-1H6v-2.5a.5.5 0 1 1 1 0V17h2.497a.5.5 0 0 1 0 1H7Z",
|
||||||
"warning-outline": "M10.91 2.782a2.25 2.25 0 0 1 2.975.74l.083.138 7.759 14.009a2.25 2.25 0 0 1-1.814 3.334l-.154.006H4.243a2.25 2.25 0 0 1-2.041-3.197l.072-.143L10.031 3.66a2.25 2.25 0 0 1 .878-.878Zm9.505 15.613-7.76-14.008a.75.75 0 0 0-1.254-.088l-.057.088-7.757 14.008a.75.75 0 0 0 .561 1.108l.095.006h15.516a.75.75 0 0 0 .696-1.028l-.04-.086-7.76-14.008 7.76 14.008ZM12 16.002a.999.999 0 1 1 0 1.997.999.999 0 0 1 0-1.997ZM11.995 8.5a.75.75 0 0 1 .744.647l.007.102.004 4.502a.75.75 0 0 1-1.494.103l-.006-.102-.004-4.502a.75.75 0 0 1 .75-.75Z",
|
"warning-outline": "M10.91 2.782a2.25 2.25 0 0 1 2.975.74l.083.138 7.759 14.009a2.25 2.25 0 0 1-1.814 3.334l-.154.006H4.243a2.25 2.25 0 0 1-2.041-3.197l.072-.143L10.031 3.66a2.25 2.25 0 0 1 .878-.878Zm9.505 15.613-7.76-14.008a.75.75 0 0 0-1.254-.088l-.057.088-7.757 14.008a.75.75 0 0 0 .561 1.108l.095.006h15.516a.75.75 0 0 0 .696-1.028l-.04-.086-7.76-14.008 7.76 14.008ZM12 16.002a.999.999 0 1 1 0 1.997.999.999 0 0 1 0-1.997ZM11.995 8.5a.75.75 0 0 1 .744.647l.007.102.004 4.502a.75.75 0 0 1-1.494.103l-.006-.102-.004-4.502a.75.75 0 0 1 .75-.75Z",
|
||||||
"wifi-off-outline": "m12.858 14.273 7.434 7.434a1 1 0 0 0 1.414-1.414l-17.999-18a1 1 0 1 0-1.414 1.414L5.39 6.804c-.643.429-1.254.927-1.821 1.495a12.382 12.382 0 0 0-1.39 1.683 1 1 0 0 0 1.644 1.14c.363-.524.761-1.01 1.16-1.41a9.94 9.94 0 0 1 1.855-1.46L7.99 9.405a8.14 8.14 0 0 0-3.203 3.377 1 1 0 0 0 1.784.903 6.08 6.08 0 0 1 1.133-1.563 6.116 6.116 0 0 1 1.77-1.234l1.407 1.407A5.208 5.208 0 0 0 8.336 13.7a5.25 5.25 0 0 0-1.09 1.612 1 1 0 0 0 1.832.802c.167-.381.394-.722.672-1a3.23 3.23 0 0 1 3.108-.841Zm-1.332-5.93 2.228 2.229a6.1 6.1 0 0 1 2.616 1.55c.444.444.837.995 1.137 1.582a1 1 0 1 0 1.78-.911 8.353 8.353 0 0 0-1.503-2.085 8.108 8.108 0 0 0-6.258-2.365ZM8.51 5.327l1.651 1.651a9.904 9.904 0 0 1 10.016 4.148 1 1 0 1 0 1.646-1.136A11.912 11.912 0 0 0 8.51 5.327Zm4.552 11.114a1.501 1.501 0 1 1-2.123 2.123 1.501 1.501 0 0 1 2.123-2.123Z",
|
"wifi-off-outline": "m12.858 14.273 7.434 7.434a1 1 0 0 0 1.414-1.414l-17.999-18a1 1 0 1 0-1.414 1.414L5.39 6.804c-.643.429-1.254.927-1.821 1.495a12.382 12.382 0 0 0-1.39 1.683 1 1 0 0 0 1.644 1.14c.363-.524.761-1.01 1.16-1.41a9.94 9.94 0 0 1 1.855-1.46L7.99 9.405a8.14 8.14 0 0 0-3.203 3.377 1 1 0 0 0 1.784.903 6.08 6.08 0 0 1 1.133-1.563 6.116 6.116 0 0 1 1.77-1.234l1.407 1.407A5.208 5.208 0 0 0 8.336 13.7a5.25 5.25 0 0 0-1.09 1.612 1 1 0 0 0 1.832.802c.167-.381.394-.722.672-1a3.23 3.23 0 0 1 3.108-.841Zm-1.332-5.93 2.228 2.229a6.1 6.1 0 0 1 2.616 1.55c.444.444.837.995 1.137 1.582a1 1 0 1 0 1.78-.911 8.353 8.353 0 0 0-1.503-2.085 8.108 8.108 0 0 0-6.258-2.365ZM8.51 5.327l1.651 1.651a9.904 9.904 0 0 1 10.016 4.148 1 1 0 1 0 1.646-1.136A11.912 11.912 0 0 0 8.51 5.327Zm4.552 11.114a1.501 1.501 0 1 1-2.123 2.123 1.501 1.501 0 0 1 2.123-2.123Z",
|
||||||
"whatsapp-outline": "M19.05 4.91A9.816 9.816 0 0 0 12.04 2c-5.46 0-9.91 4.45-9.91 9.91c0 1.75.46 3.45 1.32 4.95L2.05 22l5.25-1.38c1.45.79 3.08 1.21 4.74 1.21c5.46 0 9.91-4.45 9.91-9.91c0-2.65-1.03-5.14-2.9-7.01zm-7.01 15.24c-1.48 0-2.93-.4-4.2-1.15l-.3-.18l-3.12.82l.83-3.04l-.2-.31a8.264 8.264 0 0 1-1.26-4.38c0-4.54 3.7-8.24 8.24-8.24c2.2 0 4.27.86 5.82 2.42a8.183 8.183 0 0 1 2.41 5.83c.02 4.54-3.68 8.23-8.22 8.23zm4.52-6.16c-.25-.12-1.47-.72-1.69-.81c-.23-.08-.39-.12-.56.12c-.17.25-.64.81-.78.97c-.14.17-.29.19-.54.06c-.25-.12-1.05-.39-1.99-1.23c-.74-.66-1.23-1.47-1.38-1.72c-.14-.25-.02-.38.11-.51c.11-.11.25-.29.37-.43s.17-.25.25-.41c.08-.17.04-.31-.02-.43s-.56-1.34-.76-1.84c-.2-.48-.41-.42-.56-.43h-.48c-.17 0-.43.06-.66.31c-.22.25-.86.85-.86 2.07c0 1.22.89 2.4 1.01 2.56c.12.17 1.75 2.67 4.23 3.74c.59.26 1.05.41 1.41.52c.59.19 1.13.16 1.56.1c.48-.07 1.47-.6 1.67-1.18c.21-.58.21-1.07.14-1.18s-.22-.16-.47-.28z",
|
"whatsapp-outline": "M19.05 4.91A9.816 9.816 0 0 0 12.04 2c-5.46 0-9.91 4.45-9.91 9.91c0 1.75.46 3.45 1.32 4.95L2.05 22l5.25-1.38c1.45.79 3.08 1.21 4.74 1.21c5.46 0 9.91-4.45 9.91-9.91c0-2.65-1.03-5.14-2.9-7.01zm-7.01 15.24c-1.48 0-2.93-.4-4.2-1.15l-.3-.18l-3.12.82l.83-3.04l-.2-.31a8.264 8.264 0 0 1-1.26-4.38c0-4.54 3.7-8.24 8.24-8.24c2.2 0 4.27.86 5.82 2.42a8.183 8.183 0 0 1 2.41 5.83c.02 4.54-3.68 8.23-8.22 8.23zm4.52-6.16c-.25-.12-1.47-.72-1.69-.81c-.23-.08-.39-.12-.56.12c-.17.25-.64.81-.78.97c-.14.17-.29.19-.54.06c-.25-.12-1.05-.39-1.99-1.23c-.74-.66-1.23-1.47-1.38-1.72c-.14-.25-.02-.38.11-.51c.11-.11.25-.29.37-.43s.17-.25.25-.41c.08-.17.04-.31-.02-.43s-.56-1.34-.76-1.84c-.2-.48-.41-.42-.56-.43h-.48c-.17 0-.43.06-.66.31c-.22.25-.86.85-.86 2.07c0 1.22.89 2.4 1.01 2.56c.12.17 1.75 2.67 4.23 3.74c.59.26 1.05.41 1.41.52c.59.19 1.13.16 1.56.1c.48-.07 1.47-.6 1.67-1.18c.21-.58.21-1.07.14-1.18s-.22-.16-.47-.28z",
|
||||||
|
|||||||
Reference in New Issue
Block a user