diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d91d85b0d..a2cb466f1 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -65,6 +65,7 @@ class DashboardController < ActionController::Base VAPID_PUBLIC_KEY: VapidService.public_key, ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'), FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''), + INSTAGRAM_APP_ID: GlobalConfigService.load('INSTAGRAM_APP_ID', ''), FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'), IS_ENTERPRISE: ChatwootApp.enterprise?, AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''), diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue index 80f328980..4f9751c4e 100644 --- a/app/javascript/dashboard/components/widgets/ChannelItem.vue +++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue @@ -17,6 +17,9 @@ export default { hasFbConfigured() { return window.chatwootConfig?.fbAppId; }, + hasInstagramConfigured() { + return window.chatwootConfig?.instagramAppId; + }, isActive() { const { key } = this.channel; if (Object.keys(this.enabledFeatures).length === 0) { @@ -33,7 +36,9 @@ export default { } if (key === 'instagram') { - return this.enabledFeatures.channel_instagram; + return ( + this.enabledFeatures.channel_instagram && this.hasInstagramConfigured + ); } return [ diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 1c7805713..ce47553ea 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -686,7 +686,11 @@ export default { this.isATwilioWhatsAppChannel || this.isAWhatsAppCloudChannel || this.is360DialogWhatsAppChannel; - if (isOnWhatsApp && !this.isPrivate) { + // When users send messages containing both text and attachments on Instagram, Instagram treats them as separate messages. + // Although Chatwoot combines these into a single message, Instagram sends separate echo events for each component. + // This can create duplicate messages in Chatwoot. To prevent this issue, we'll handle text and attachments as separate messages. + const isOnInstagram = this.isAInstagramChannel; + if ((isOnWhatsApp || isOnInstagram) && !this.isPrivate) { this.sendMessageAsMultipleMessages(this.message); } else { const messagePayload = this.getMessagePayload(this.message); @@ -703,7 +707,7 @@ export default { } }, sendMessageAsMultipleMessages(message) { - const messages = this.getMessagePayloadForWhatsapp(message); + const messages = this.getMultipleMessagesPayload(message); messages.forEach(messagePayload => { this.sendMessage(messagePayload); }); @@ -935,11 +939,11 @@ export default { return payload; }, - getMessagePayloadForWhatsapp(message) { + getMultipleMessagesPayload(message) { const multipleMessagePayload = []; if (this.attachedFiles && this.attachedFiles.length) { - let caption = message; + let caption = this.isAInstagramChannel ? '' : message; this.attachedFiles.forEach(attachment => { const attachedFile = this.globalConfig.directUploadsEnabled ? attachment.blobSignedId @@ -954,9 +958,19 @@ export default { attachmentPayload = this.setReplyToInPayload(attachmentPayload); multipleMessagePayload.push(attachmentPayload); - caption = ''; + // For WhatsApp, only the first attachment gets a caption + if (!this.isAInstagramChannel) caption = ''; }); - } else { + } + + const hasNoAttachments = + !this.attachedFiles || !this.attachedFiles.length; + // For Instagram, we need a separate text message + // For WhatsApp, we only need a text message if there are no attachments + if ( + (this.isAInstagramChannel && this.message) || + (!this.isAInstagramChannel && hasNoAttachments) + ) { let messagePayload = { conversationId: this.currentChat.id, message, @@ -1136,7 +1150,7 @@ export default { v-else-if="!showRichContentEditor" ref="messageInput" v-model="message" - class="input rounded-none" + class="rounded-none input" :placeholder="messagePlaceHolder" :min-height="4" :signature="signatureToApply" diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue index a404cbea6..8482dabc2 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue @@ -35,8 +35,7 @@ export default { }, { key: 'telegram', name: 'Telegram' }, { key: 'line', name: 'Line' }, - // TODO: Add Instagram to the channel list after the feature is ready to use. - // { key: 'instagram', name: 'Instagram' }, + { key: 'instagram', name: 'Instagram' }, ]; }, ...mapGetters({ diff --git a/app/views/layouts/vueapp.html.erb b/app/views/layouts/vueapp.html.erb index 4312638ed..4be41f8b2 100644 --- a/app/views/layouts/vueapp.html.erb +++ b/app/views/layouts/vueapp.html.erb @@ -35,6 +35,7 @@ hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>', helpCenterURL: '<%= ENV.fetch('HELPCENTER_URL', '') %>', fbAppId: '<%= @global_config['FB_APP_ID'] %>', + instagramAppId: '<%= @global_config['INSTAGRAM_APP_ID'] %>', googleOAuthClientId: '<%= ENV.fetch('GOOGLE_OAUTH_CLIENT_ID', nil) %>', googleOAuthCallbackUrl: '<%= ENV.fetch('GOOGLE_OAUTH_CALLBACK_URL', nil) %>', fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>', diff --git a/config/features.yml b/config/features.yml index 3396733ab..7cac723c2 100644 --- a/config/features.yml +++ b/config/features.yml @@ -163,5 +163,5 @@ enabled: false - name: channel_instagram display_name: Instagram Channel - enabled: false + enabled: true chatwoot_internal: true