diff --git a/Gemfile.lock b/Gemfile.lock index ad2a96921..141afc122 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,6 +136,8 @@ GEM audited (5.4.1) activerecord (>= 5.0, < 7.7) activesupport (>= 5.0, < 7.7) + auth-sanitizer (0.2.1) + version_gem (~> 1.1, >= 1.1.10) aws-actionmailbox-ses (0.1.0) actionmailbox (>= 7.1.0) aws-sdk-s3 (~> 1, >= 1.123.0) @@ -168,7 +170,7 @@ GEM base64 (0.3.0) bcrypt (3.1.22) benchmark (0.4.1) - bigdecimal (3.2.2) + bigdecimal (3.3.1) bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) @@ -184,6 +186,7 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) + cgi (0.5.1) childprocess (5.1.0) logger (~> 1.5) cld3 (3.7.0) @@ -312,7 +315,7 @@ GEM hashie faraday-multipart (1.2.0) multipart-post (~> 2.0) - faraday-net_http (3.4.2) + faraday-net_http (3.4.4) net-http (~> 0.5) faraday-net_http_persistent (2.1.0) faraday (~> 2.5) @@ -435,7 +438,8 @@ GEM hana (1.3.7) hash_diff (1.1.1) hashdiff (1.1.0) - hashie (5.0.0) + hashie (5.1.0) + logger html2text (0.4.0) nokogiri (>= 1.0, < 2.0) http (5.1.1) @@ -470,7 +474,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.19.5) + json (2.19.8) json_refs (0.1.8) hana json_schemer (0.2.24) @@ -568,7 +572,7 @@ GEM ruby2_keywords msgpack (1.8.0) multi_json (1.15.0) - multi_xml (0.8.0) + multi_xml (0.9.1) bigdecimal (>= 3.1, < 5) multipart-post (2.4.1) mutex_m (0.3.0) @@ -603,19 +607,26 @@ GEM racc (~> 1.4) nokogiri (1.19.3-x86_64-linux-gnu) racc (~> 1.4) - oauth (1.1.0) - oauth-tty (~> 1.0, >= 1.0.1) - snaky_hash (~> 2.0) - version_gem (~> 1.1) - oauth-tty (1.0.5) - version_gem (~> 1.1, >= 1.1.1) - oauth2 (2.0.9) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) + oauth (1.1.6) + auth-sanitizer (~> 0.2, >= 0.2.1) + base64 (~> 0.1) + cgi + oauth-tty (~> 1.0, >= 1.0.8) + snaky_hash (~> 2.0, >= 2.0.5) + version_gem (~> 1.1, >= 1.1.11) + oauth-tty (1.0.8) + auth-sanitizer (~> 0.1, >= 0.1.3) + cgi + version_gem (~> 1.1, >= 1.1.9) + oauth2 (2.0.22) + auth-sanitizer (~> 0.2, >= 0.2.1) + faraday (>= 0.17.3, < 4.0) + jwt (>= 1.0, < 4.0) + logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0) - version_gem (~> 1.1) + snaky_hash (~> 2.0, >= 2.0.5) + version_gem (~> 1.1, >= 1.1.11) oj (3.16.10) bigdecimal (>= 3.0) ostruct (>= 0.2) @@ -935,9 +946,9 @@ GEM gli hashie logger - snaky_hash (2.0.1) - hashie - version_gem (~> 1.1, >= 1.1.1) + snaky_hash (2.0.5) + hashie (>= 0.1.0, < 6) + version_gem (>= 1.1.8, < 3) sorbet-runtime (0.5.11934) spring (4.1.1) spring-watcher-listen (2.1.0) @@ -995,7 +1006,7 @@ GEM valid_email2 (5.2.6) activemodel (>= 3.2) mail (~> 2.5) - version_gem (1.1.4) + version_gem (1.1.11) vite_rails (3.10.0) railties (>= 5.1, < 9) vite_ruby (~> 3.0, >= 3.2.2) diff --git a/VERSION_CW b/VERSION_CW index d2b9909a9..0fb7a35b6 100644 --- a/VERSION_CW +++ b/VERSION_CW @@ -1 +1 @@ -4.14.1 +4.14.2 diff --git a/app/builders/contact_inbox_with_contact_builder.rb b/app/builders/contact_inbox_with_contact_builder.rb index 2c0e6087e..2ba9179ed 100644 --- a/app/builders/contact_inbox_with_contact_builder.rb +++ b/app/builders/contact_inbox_with_contact_builder.rb @@ -50,7 +50,7 @@ class ContactInboxWithContactBuilder def create_contact account.contacts.create!( - name: contact_attributes[:name] || ::Haikunator.haikunate(1000), + name: contact_name, phone_number: contact_attributes[:phone_number], email: contact_attributes[:email], identifier: contact_attributes[:identifier], @@ -59,6 +59,11 @@ class ContactInboxWithContactBuilder ) end + def contact_name + name = contact_attributes[:name] || ::Haikunator.haikunate(1000) + name.truncate(ApplicationRecord::MAX_STRING_COLUMN_LENGTH, omission: '') + end + def find_contact contact = find_contact_by_identifier(contact_attributes[:identifier]) contact ||= find_contact_by_email(contact_attributes[:email]) diff --git a/app/controllers/api/v1/accounts/onboardings_controller.rb b/app/controllers/api/v1/accounts/onboardings_controller.rb index 77c6245ea..181e4965e 100644 --- a/app/controllers/api/v1/accounts/onboardings_controller.rb +++ b/app/controllers/api/v1/accounts/onboardings_controller.rb @@ -16,6 +16,10 @@ class Api::V1::Accounts::OnboardingsController < Api::V1::Accounts::BaseControll render 'api/v1/accounts/update', format: :json end + def help_center_generation + render json: help_center_generation_status + end + private def finalizing_account_details? @@ -33,4 +37,15 @@ class Api::V1::Accounts::OnboardingsController < Api::V1::Accounts::BaseControll def custom_attributes_params params.permit(:industry, :company_size, :timezone, :referral_source, :user_role, :website) end + + def help_center_generation_status + { + generation_id: nil, + state: nil, + articles_count: 0, + categories_count: 0 + } + end end + +Api::V1::Accounts::OnboardingsController.prepend_mod_with('Api::V1::Accounts::OnboardingsController') diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index fa437327d..1c27d8260 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -40,7 +40,7 @@ class ConversationFinder def perform set_up - mine_count, unassigned_count, all_count, = set_count_for_all_conversations + mine_count, unassigned_count, all_count = set_count_for_all_conversations assigned_count = all_count - unassigned_count filter_by_assignee_type @@ -184,6 +184,17 @@ class ConversationFinder end def set_count_for_all_conversations + return legacy_count_for_all_conversations if @conversations.limit_value || @conversations.offset_value || @conversations.eager_loading? + + counts = @conversations.unscope(:order).pick( + Arel.sql("COUNT(*) FILTER (WHERE assignee_id = #{current_user.id})"), + Arel.sql('COUNT(*) FILTER (WHERE assignee_id IS NULL)'), + Arel.sql('COUNT(*)') + ) + counts || [0, 0, 0] + end + + def legacy_count_for_all_conversations [ @conversations.assigned_to(current_user).count, @conversations.unassigned.count, diff --git a/app/javascript/dashboard/api/inboxes.js b/app/javascript/dashboard/api/inboxes.js index 114dbb6f4..3c1d17fc8 100644 --- a/app/javascript/dashboard/api/inboxes.js +++ b/app/javascript/dashboard/api/inboxes.js @@ -60,6 +60,12 @@ class Inboxes extends CacheEnabledApiClient { disableWhatsappCalling(inboxId) { return axios.post(`${this.url}/${inboxId}/disable_whatsapp_calling`); } + + setInboundCalls(inboxId, enabled) { + return axios.post(`${this.url}/${inboxId}/set_inbound_calls`, { + inbound_calls_enabled: enabled, + }); + } } export default new Inboxes(); diff --git a/app/javascript/dashboard/api/onboarding.js b/app/javascript/dashboard/api/onboarding.js index 2bc098eef..e15d16da3 100644 --- a/app/javascript/dashboard/api/onboarding.js +++ b/app/javascript/dashboard/api/onboarding.js @@ -9,6 +9,10 @@ class OnboardingAPI extends ApiClient { update(data) { return axios.patch(this.url, data); } + + getHelpCenterGeneration() { + return axios.get(`${this.url}/help_center_generation`); + } } export default new OnboardingAPI(); diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue index d9f523dfc..f71652ca0 100644 --- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue +++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue @@ -175,6 +175,9 @@ useEventListener(document, 'touchend', onResizeEnd); const inboxes = useMapGetter('inboxes/getInboxes'); const labels = useMapGetter('labels/getLabelsOnSidebar'); +const allUnreadCount = useMapGetter( + 'conversationUnreadCounts/getAllUnreadCount' +); const getInboxUnreadCount = useMapGetter( 'conversationUnreadCounts/getInboxUnreadCount' ); @@ -297,6 +300,7 @@ const menuItems = computed(() => { { name: 'All', label: t('SIDEBAR.ALL_CONVERSATIONS'), + badgeCount: allUnreadCount.value, activeOn: ['inbox_conversation'], to: accountScopedRoute('home'), }, diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 17f5559f1..fc93ff2c7 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -375,10 +375,10 @@ export default { return `draft-${this.conversationIdByRoute}-${this.replyType}`; }, audioRecordFormat() { - if (this.isAWhatsAppChannel) { + if (this.isAWhatsAppCloudChannel) { return AUDIO_FORMATS.OGG; } - if (this.isATelegramChannel) { + if (this.isAWhatsAppChannel || this.isATelegramChannel) { return AUDIO_FORMATS.MP3; } if (this.isAPIInbox) { diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index cd7e46330..574cf5b9b 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -653,6 +653,10 @@ }, "CREDENTIALS": { "DESCRIPTION": "Voice calling requires Twilio API Key credentials. These are used to generate tokens for agent voice connections." + }, + "INBOUND": { + "LABEL": "Allow incoming calls", + "DESCRIPTION": "Let customers call this number. When turned off, incoming calls are declined automatically — agents aren't notified and no conversation is created. Agents can still place outgoing calls." } }, "WHATSAPP_CALLING": { diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue index 0502ebc1b..55c1e7f03 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue @@ -94,8 +94,18 @@ export default { return this.getAccount(this.accountId) || {}; }, }, + watch: { + 'currentAccount.id'(id) { + if (id) { + this.initializeAccount(); + } + }, + }, mounted() { - this.initializeAccount(); + // Account already in the store (navigated in): seed immediately. + if (this.currentAccount.id) { + this.initializeAccount(); + } }, methods: { async initializeAccount() { diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/components/AccountId.vue b/app/javascript/dashboard/routes/dashboard/settings/account/components/AccountId.vue index f02efcdc2..941d79fb1 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/account/components/AccountId.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/account/components/AccountId.vue @@ -8,7 +8,7 @@ import SectionLayout from './SectionLayout.vue'; const { t } = useI18n(); const { currentAccount } = useAccount(); -const getAccountId = computed(() => currentAccount.value.id.toString()); +const getAccountId = computed(() => currentAccount.value?.id?.toString());