From d902bb1d6f890e8b8edcf465171721a9bd803c48 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Sat, 7 Dec 2024 02:01:01 +0530 Subject: [PATCH 01/13] fix: Remove duplicate contactable inbox in the conversation form (#10554) --------- Co-authored-by: Pranav --- .../api/v1/accounts/contacts_controller.rb | 2 +- app/javascript/dashboard/api/contacts.js | 8 +++ .../Contacts/Pages/ContactDetails.vue | 4 ++ .../NewConversation/ComposeConversation.vue | 1 - .../helpers/composeConversationHelper.js | 69 +++++++++++++----- .../specs/composeConversationHelper.spec.js | 71 +++++++++++++++++++ .../contacts/pages/ContactManageView.vue | 2 +- .../v1/accounts/contacts/show.json.jbuilder | 2 +- .../v1/accounts/contacts/update.json.jbuilder | 2 +- 9 files changed, 137 insertions(+), 24 deletions(-) diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 250c64a86..0d8e0ed93 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -14,7 +14,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController before_action :check_authorization before_action :set_current_page, only: [:index, :active, :search, :filter] before_action :fetch_contact, only: [:show, :update, :destroy, :avatar, :contactable_inboxes, :destroy_custom_attributes] - before_action :set_include_contact_inboxes, only: [:index, :search, :filter] + before_action :set_include_contact_inboxes, only: [:index, :search, :filter, :show, :update] def index @contacts_count = resolved_contacts.count diff --git a/app/javascript/dashboard/api/contacts.js b/app/javascript/dashboard/api/contacts.js index 85c4bba1d..2eee3f484 100644 --- a/app/javascript/dashboard/api/contacts.js +++ b/app/javascript/dashboard/api/contacts.js @@ -27,6 +27,14 @@ class ContactAPI extends ApiClient { return axios.get(requestURL); } + show(id) { + return axios.get(`${this.url}/${id}?include_contact_inboxes=false`); + } + + update(id, data) { + return axios.patch(`${this.url}/${id}?include_contact_inboxes=false`, data); + } + getConversations(contactId) { return axios.get(`${this.url}/${contactId}/conversations`); } diff --git a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue index d46ffd850..aec21a976 100644 --- a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue +++ b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue @@ -70,6 +70,10 @@ const updateContact = async () => { try { const { customAttributes, ...basicContactData } = contactData.value; await store.dispatch('contacts/update', basicContactData); + await store.dispatch( + 'contacts/fetchContactableInbox', + props.selectedContact.id + ); useAlert(t('CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.SUCCESS_MESSAGE')); } catch (error) { useAlert(t('CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.ERROR_MESSAGE')); diff --git a/app/javascript/dashboard/components-next/NewConversation/ComposeConversation.vue b/app/javascript/dashboard/components-next/NewConversation/ComposeConversation.vue index 2ec22140b..b5d57f202 100644 --- a/app/javascript/dashboard/components-next/NewConversation/ComposeConversation.vue +++ b/app/javascript/dashboard/components-next/NewConversation/ComposeConversation.vue @@ -153,7 +153,6 @@ watch( activeContact, () => { if (activeContact.value && props.contactId) { - // Add null check for contactInboxes const contactInboxes = activeContact.value?.contactInboxes || []; selectedContact.value = { ...activeContact.value, diff --git a/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js b/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js index c6bb9b526..12a5e76b3 100644 --- a/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js +++ b/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js @@ -3,6 +3,15 @@ import { getInboxIconByType } from 'dashboard/helper/inbox'; import camelcaseKeys from 'camelcase-keys'; import ContactAPI from 'dashboard/api/contacts'; +const CHANNEL_PRIORITY = { + 'Channel::Email': 1, + 'Channel::Whatsapp': 2, + 'Channel::Sms': 3, + 'Channel::TwilioSms': 4, + 'Channel::WebWidget': 5, + 'Channel::Api': 6, +}; + export const generateLabelForContactableInboxesList = ({ name, email, @@ -21,27 +30,49 @@ export const generateLabelForContactableInboxesList = ({ return name; }; +const transformInbox = ({ + name, + id, + email, + channelType, + phoneNumber, + ...rest +}) => ({ + id, + icon: getInboxIconByType(channelType, phoneNumber, 'line'), + label: generateLabelForContactableInboxesList({ + name, + email, + channelType, + phoneNumber, + }), + action: 'inbox', + value: id, + name, + email, + phoneNumber, + channelType, + ...rest, +}); + +export const compareInboxes = (a, b) => { + // Channels that have no priority defined should come at the end. + const priorityA = CHANNEL_PRIORITY[a.channelType] || 999; + const priorityB = CHANNEL_PRIORITY[b.channelType] || 999; + + if (priorityA !== priorityB) { + return priorityA - priorityB; + } + + const nameA = a.name || ''; + const nameB = b.name || ''; + return nameA.localeCompare(nameB); +}; + export const buildContactableInboxesList = contactInboxes => { if (!contactInboxes) return []; - return contactInboxes.map( - ({ name, id, email, channelType, phoneNumber, ...rest }) => ({ - id, - icon: getInboxIconByType(channelType, phoneNumber, 'line'), - label: generateLabelForContactableInboxesList({ - name, - email, - channelType, - phoneNumber, - }), - action: 'inbox', - value: id, - name, - email, - phoneNumber, - channelType, - ...rest, - }) - ); + + return contactInboxes.map(transformInbox).sort(compareInboxes); }; export const getCapitalizedNameFromEmail = email => { diff --git a/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js b/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js index 54c4eab60..5a2d0092d 100644 --- a/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js +++ b/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js @@ -463,3 +463,74 @@ describe('composeConversationHelper', () => { }); }); }); + +describe('compareInboxes', () => { + it('should sort inboxes by channel priority', () => { + const inboxes = [ + { channelType: 'Channel::Api', name: 'API Inbox' }, + { channelType: 'Channel::Email', name: 'Email Inbox' }, + { channelType: 'Channel::WebWidget', name: 'Widget' }, + { channelType: 'Channel::Whatsapp', name: 'WhatsApp' }, + ]; + + const sorted = [...inboxes].sort(helpers.compareInboxes); + + expect(sorted[0].channelType).toBe('Channel::Email'); + expect(sorted[1].channelType).toBe('Channel::Whatsapp'); + expect(sorted[2].channelType).toBe('Channel::WebWidget'); + expect(sorted[3].channelType).toBe('Channel::Api'); + }); + + it('should sort SMS channels correctly', () => { + const inboxes = [ + { channelType: 'Channel::TwilioSms', name: 'Twilio' }, + { channelType: 'Channel::Sms', name: 'Regular SMS' }, + ]; + + const sorted = [...inboxes].sort(helpers.compareInboxes); + + expect(sorted[0].channelType).toBe('Channel::Sms'); + expect(sorted[1].channelType).toBe('Channel::TwilioSms'); + }); + + it('should sort by name when channel types are same', () => { + const inboxes = [ + { channelType: 'Channel::Email', name: 'Support' }, + { channelType: 'Channel::Email', name: 'Marketing' }, + { channelType: 'Channel::Email', name: 'Billing' }, + ]; + + const sorted = [...inboxes].sort(helpers.compareInboxes); + + expect(sorted.map(inbox => inbox.name)).toEqual([ + 'Billing', + 'Marketing', + 'Support', + ]); + }); + + it('should put channels without priority at the end', () => { + const inboxes = [ + { channelType: 'Channel::Unknown', name: 'Unknown' }, + { channelType: 'Channel::Email', name: 'Email' }, + { channelType: 'Channel::LineChannel', name: 'Line' }, + { channelType: 'Channel::Whatsapp', name: 'WhatsApp' }, + ]; + + const sorted = [...inboxes].sort(helpers.compareInboxes); + + expect(sorted.map(i => i.channelType)).toEqual([ + 'Channel::Email', + 'Channel::Whatsapp', + + 'Channel::LineChannel', + 'Channel::Unknown', + ]); + }); + + it('should handle empty array', () => { + const inboxes = []; + const sorted = [...inboxes].sort(helpers.compareInboxes); + expect(sorted).toEqual([]); + }); +}); diff --git a/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue b/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue index 8b07646e0..9f99157e6 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue @@ -62,7 +62,7 @@ const goToContactsList = () => { const fetchActiveContact = async () => { if (route.params.contactId) { - store.dispatch('contacts/show', { id: route.params.contactId }); + await store.dispatch('contacts/show', { id: route.params.contactId }); await store.dispatch( 'contacts/fetchContactableInbox', route.params.contactId diff --git a/app/views/api/v1/accounts/contacts/show.json.jbuilder b/app/views/api/v1/accounts/contacts/show.json.jbuilder index 524a393bf..1add73326 100644 --- a/app/views/api/v1/accounts/contacts/show.json.jbuilder +++ b/app/views/api/v1/accounts/contacts/show.json.jbuilder @@ -1,3 +1,3 @@ json.payload do - json.partial! 'api/v1/models/contact', formats: [:json], resource: @contact, with_contact_inboxes: true + json.partial! 'api/v1/models/contact', formats: [:json], resource: @contact, with_contact_inboxes: @include_contact_inboxes end diff --git a/app/views/api/v1/accounts/contacts/update.json.jbuilder b/app/views/api/v1/accounts/contacts/update.json.jbuilder index 524a393bf..1add73326 100644 --- a/app/views/api/v1/accounts/contacts/update.json.jbuilder +++ b/app/views/api/v1/accounts/contacts/update.json.jbuilder @@ -1,3 +1,3 @@ json.payload do - json.partial! 'api/v1/models/contact', formats: [:json], resource: @contact, with_contact_inboxes: true + json.partial! 'api/v1/models/contact', formats: [:json], resource: @contact, with_contact_inboxes: @include_contact_inboxes end From 2ce7c8b84544b7d2ba13fc66618543e83bd348bc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 9 Dec 2024 15:59:26 +0530 Subject: [PATCH 02/13] fix: sidebar collapsed on reload (#10561) When reloading a page, the sidebar item that is actively selected is collapsed by default. This PR fixes it by expanding it on reload --- .../dashboard/components-next/sidebar/SidebarGroup.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue b/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue index 051375b68..f22e003f1 100644 --- a/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue +++ b/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue @@ -1,5 +1,5 @@ From 472f6d9345fa6cfafb07e055b2931026988cc1d7 Mon Sep 17 00:00:00 2001 From: giquieu Date: Mon, 9 Dec 2024 12:36:17 -0300 Subject: [PATCH 03/13] feat: Ability to lock the conversation to a single thread in API channels (#10329) Added the possibility to mark as a single conversation in the API type inbox. This allows the conversation builder to search for the last conversation. I thought about searching for the last conversation with created_at: desc order, as is done in some channels... but I didn't change the way the conversation is searched. Fixes: #7726 Co-authored-by: Sojan Jose --- .../dashboard/settings/inbox/Settings.vue | 5 +- spec/builders/conversation_builder_spec.rb | 62 +++++++++++++++---- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 530106f0a..b91fc2414 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -175,7 +175,10 @@ export default { }, canLocktoSingleConversation() { return ( - this.isASmsInbox || this.isAWhatsAppChannel || this.isAFacebookInbox + this.isASmsInbox || + this.isAWhatsAppChannel || + this.isAFacebookInbox || + this.isAPIInbox ); }, inboxNameLabel() { diff --git a/spec/builders/conversation_builder_spec.rb b/spec/builders/conversation_builder_spec.rb index 956aec966..c0c5f248b 100644 --- a/spec/builders/conversation_builder_spec.rb +++ b/spec/builders/conversation_builder_spec.rb @@ -3,39 +3,77 @@ require 'rails_helper' describe ConversationBuilder do let(:account) { create(:account) } let!(:sms_channel) { create(:channel_sms, account: account) } + let!(:api_channel) { create(:channel_api, account: account) } let!(:sms_inbox) { create(:inbox, channel: sms_channel, account: account) } + let!(:api_inbox) { create(:inbox, channel: api_channel, account: account) } let(:contact) { create(:contact, account: account) } - let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: sms_inbox) } + let(:contact_sms_inbox) { create(:contact_inbox, contact: contact, inbox: sms_inbox) } + let(:contact_api_inbox) { create(:contact_inbox, contact: contact, inbox: api_inbox) } describe '#perform' do - it 'creates conversation' do + it 'creates sms conversation' do conversation = described_class.new( - contact_inbox: contact_inbox, + contact_inbox: contact_sms_inbox, params: {} ).perform - expect(conversation.contact_inbox_id).to eq(contact_inbox.id) + expect(conversation.contact_inbox_id).to eq(contact_sms_inbox.id) end - context 'when lock_to_single_conversation is true for inbox' do + it 'creates api conversation' do + conversation = described_class.new( + contact_inbox: contact_api_inbox, + params: {} + ).perform + + expect(conversation.contact_inbox_id).to eq(contact_api_inbox.id) + end + + context 'when lock_to_single_conversation is true for sms inbox' do before do sms_inbox.update!(lock_to_single_conversation: true) end - it 'creates conversation when existing conversation is not present' do + it 'creates sms conversation when existing conversation is not present' do conversation = described_class.new( - contact_inbox: contact_inbox, + contact_inbox: contact_sms_inbox, params: {} ).perform - expect(conversation.contact_inbox_id).to eq(contact_inbox.id) + expect(conversation.contact_inbox_id).to eq(contact_sms_inbox.id) end - it 'returns last from existing conversations when existing conversation is not present' do - create(:conversation, contact_inbox: contact_inbox) - existing_conversation = create(:conversation, contact_inbox: contact_inbox) + it 'returns last from existing sms conversations when existing conversation is not present' do + create(:conversation, contact_inbox: contact_sms_inbox) + existing_conversation = create(:conversation, contact_inbox: contact_sms_inbox) conversation = described_class.new( - contact_inbox: contact_inbox, + contact_inbox: contact_sms_inbox, + params: {} + ).perform + + expect(conversation.id).to eq(existing_conversation.id) + end + end + + context 'when lock_to_single_conversation is true for api inbox' do + before do + api_inbox.update!(lock_to_single_conversation: true) + end + + it 'creates conversation when existing api conversation is not present' do + conversation = described_class.new( + contact_inbox: contact_api_inbox, + params: {} + ).perform + + expect(conversation.contact_inbox_id).to eq(contact_api_inbox.id) + end + + it 'returns last from existing api conversations when existing conversation is not present' do + create(:conversation, contact_inbox: contact_api_inbox) + existing_conversation = create(:conversation, contact_inbox: contact_api_inbox) + conversation = described_class.new( + contact_inbox: contact_api_inbox, params: {} ).perform From aebcbb63e489921c53c297964ea45db1400480e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:48:46 -0800 Subject: [PATCH 04/13] chore(deps): bump nanoid from 3.3.7 to 3.3.8 (#10565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
Changelog

Sourced from nanoid's changelog.

3.3.8

  • Fixed a way to break Nano ID by passing non-integer size (by @โ€‹myndzi).
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nanoid&package-manager=npm_and_yarn&previous-version=3.3.7&new-version=3.3.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5642db061..03fac16f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3668,11 +3668,6 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -9073,8 +9068,6 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} - nanoid@3.3.8: {} nanoid@5.0.8: {} @@ -9564,7 +9557,7 @@ snapshots: postcss@8.4.47: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.0 source-map-js: 1.2.1 From 1b0e94ec95a43b814d39ef17aab446db435c552e Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:53:24 +0530 Subject: [PATCH 05/13] feat: Flag icon component (#10564) --- .../Contacts/ContactsCard/ContactsCard.vue | 27 ++++-- .../dashboard/components-next/flag/Flag.vue | 24 +++++ .../components-next/flag/story/Flag.story.vue | 93 +++++++++++++++++++ .../conversation/contact/ContactInfo.vue | 9 +- .../conversation/contact/ContactInfoRow.vue | 5 +- package.json | 1 + pnpm-lock.yaml | 8 ++ 7 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 app/javascript/dashboard/components-next/flag/Flag.vue create mode 100644 app/javascript/dashboard/components-next/flag/story/Flag.story.vue diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue index 9875ae2ac..a3b0bf37c 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue @@ -6,6 +6,7 @@ import CardLayout from 'dashboard/components-next/CardLayout.vue'; import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/ContactsForm.vue'; import Button from 'dashboard/components-next/button/Button.vue'; import Avatar from 'dashboard/components-next/avatar/Avatar.vue'; +import Flag from 'dashboard/components-next/flag/Flag.vue'; import countries from 'shared/constants/countries'; const props = defineProps({ @@ -56,13 +57,19 @@ const countryDetails = computed(() => { if (!activeCountry) return null; - const parts = [ - activeCountry.emoji, - city ? `${city},` : null, - activeCountry.name, - ].filter(Boolean); + return { + countryCode: activeCountry.id, + city: city ? `${city},` : null, + name: activeCountry.name, + }; +}); - return parts.length ? parts.join(' ') : null; +const formattedLocation = computed(() => { + if (!countryDetails.value) return ''; + + return [countryDetails.value.city, countryDetails.value.name] + .filter(Boolean) + .join(' '); }); const handleFormUpdate = updatedData => { @@ -114,8 +121,12 @@ const onClickViewDetails = () => emit('showContact', props.id); {{ phoneNumber }}
- - {{ countryDetails }} + + + {{ formattedLocation }}
- - diff --git a/app/javascript/dashboard/components/copilot/CopilotContainer.vue b/app/javascript/dashboard/components/copilot/CopilotContainer.vue new file mode 100644 index 000000000..940408ef8 --- /dev/null +++ b/app/javascript/dashboard/components/copilot/CopilotContainer.vue @@ -0,0 +1,58 @@ + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue index 210357a8b..f56b2a581 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue @@ -1,14 +1,14 @@ + + diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 9a6a64d6d..f9c3b784a 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -219,6 +219,10 @@ "DELETE": "Delete", "CANCEL": "Cancel" } + }, + "SIDEBAR": { + "CONTACT": "Contact", + "COPILOT": "Copilot" } }, "EMAIL_TRANSCRIPT": { diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index f6e8cbc3a..e04164902 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -299,5 +299,13 @@ "ERROR": "There was an error unlinking the issue, please try again" } } + }, + "CAPTAIN": { + "NAME": "Captain", + "COPILOT": { + "SEND_MESSAGE": "Send message...", + "LOADER": "Captain is thinking", + "YOU": "You" + } } } diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue index e998a85a1..8739b4f26 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue @@ -92,9 +92,7 @@ onMounted(() => {