diff --git a/Gemfile.lock b/Gemfile.lock index 637f5f79d..533c88f1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -502,14 +502,14 @@ GEM newrelic_rpm (9.6.0) base64 nio4r (2.7.3) - nokogiri (1.18.4) + nokogiri (1.18.8) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.4-arm64-darwin) + nokogiri (1.18.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.4-x86_64-darwin) + nokogiri (1.18.8-x86_64-darwin) racc (~> 1.4) - nokogiri (1.18.4-x86_64-linux-gnu) + nokogiri (1.18.8-x86_64-linux-gnu) racc (~> 1.4) oauth (1.1.0) oauth-tty (~> 1.0, >= 1.0.1) diff --git a/app/builders/campaigns/campaign_conversation_builder.rb b/app/builders/campaigns/campaign_conversation_builder.rb index 3b3f262c9..0e9b90105 100644 --- a/app/builders/campaigns/campaign_conversation_builder.rb +++ b/app/builders/campaigns/campaign_conversation_builder.rb @@ -9,7 +9,7 @@ class Campaigns::CampaignConversationBuilder @contact_inbox.lock! # We won't send campaigns if a conversation is already present - raise 'Conversation alread present' if @contact_inbox.reload.conversations.present? + raise 'Conversation already present' if @contact_inbox.reload.conversations.present? @conversation = ::Conversation.create!(conversation_params) Messages::MessageBuilder.new(@campaign.sender, @conversation, message_params).perform diff --git a/app/builders/messages/instagram/base_message_builder.rb b/app/builders/messages/instagram/base_message_builder.rb index 7767ee1b4..7fab7bc4d 100644 --- a/app/builders/messages/instagram/base_message_builder.rb +++ b/app/builders/messages/instagram/base_message_builder.rb @@ -94,11 +94,10 @@ class Messages::Instagram::BaseMessageBuilder < Messages::Messenger::MessageBuil def build_message # Duplicate webhook events may be sent for the same message # when a user is connected to the Instagram account through both Messenger and Instagram login. + # There is chance for echo events to be sent for the same message. # Therefore, we need to check if the message already exists before creating it. return if message_already_exists? - return if @outgoing_echo - return if message_content.blank? && all_unsupported_files? @message = conversation.messages.create!(message_params) diff --git a/app/controllers/api/v1/accounts/agent_bots_controller.rb b/app/controllers/api/v1/accounts/agent_bots_controller.rb index 43bce17bc..1422beea1 100644 --- a/app/controllers/api/v1/accounts/agent_bots_controller.rb +++ b/app/controllers/api/v1/accounts/agent_bots_controller.rb @@ -37,7 +37,7 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController end def permitted_params - params.permit(:name, :description, :outgoing_url, :avatar, :avatar_url, :bot_type, bot_config: [:csml_content]) + params.permit(:name, :description, :outgoing_url, :avatar, :avatar_url, :bot_type, bot_config: {}) end def process_avatar_from_url diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 0f915ab8a..a2cb466f1 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -36,7 +36,7 @@ class DashboardController < ActionController::Base 'LOGOUT_REDIRECT_LINK', 'DISABLE_USER_PROFILE_UPDATE', 'DEPLOYMENT_ENV', - 'CSML_EDITOR_HOST', 'INSTALLATION_PRICING_PLAN' + 'INSTALLATION_PRICING_PLAN' ).merge(app_config) end @@ -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/controllers/devise_overrides/omniauth_callbacks_controller.rb b/app/controllers/devise_overrides/omniauth_callbacks_controller.rb index e1cf76d6b..2b3ea9067 100644 --- a/app/controllers/devise_overrides/omniauth_callbacks_controller.rb +++ b/app/controllers/devise_overrides/omniauth_callbacks_controller.rb @@ -55,7 +55,7 @@ class DeviseOverrides::OmniauthCallbacksController < DeviseTokenAuth::OmniauthCa def validate_business_account? # return true if the user is a business account, false if it is a gmail account - auth_hash['info']['email'].exclude?('@gmail.com') + auth_hash['info']['email'].downcase.exclude?('@gmail.com') end def create_account_for_user diff --git a/app/javascript/dashboard/api/agentBots.js b/app/javascript/dashboard/api/agentBots.js index 4de6fcee0..6e59f38d3 100644 --- a/app/javascript/dashboard/api/agentBots.js +++ b/app/javascript/dashboard/api/agentBots.js @@ -1,9 +1,26 @@ +/* global axios */ import ApiClient from './ApiClient'; class AgentBotsAPI extends ApiClient { constructor() { super('agent_bots', { accountScoped: true }); } + + create(data) { + return axios.post(this.url, data, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } + + update(id, data) { + return axios.patch(`${this.url}/${id}`, data, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } + + deleteAgentBotAvatar(botId) { + return axios.delete(`${this.url}/${botId}/avatar`); + } } export default new AgentBotsAPI(); diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue index 5a60fefdc..34f1e003f 100644 --- a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue +++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue @@ -7,8 +7,8 @@ import { useStore, useStoreGetters } from 'dashboard/composables/store'; import { uploadFile } from 'dashboard/helper/uploadHelper'; import { checkFileSizeLimit } from 'shared/helpers/FileHelper'; import { useVuelidate } from '@vuelidate/core'; -import { required, minLength } from '@vuelidate/validators'; -import { shouldBeUrl } from 'shared/helpers/Validators'; +import { required, minLength, helpers } from '@vuelidate/validators'; +import { shouldBeUrl, isValidSlug } from 'shared/helpers/Validators'; import Button from 'dashboard/components-next/button/Button.vue'; import Input from 'dashboard/components-next/input/Input.vue'; @@ -61,7 +61,16 @@ const liveChatWidgets = computed(() => { const rules = { name: { required, minLength: minLength(2) }, - slug: { required }, + slug: { + required: helpers.withMessage( + () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR'), + required + ), + isValidSlug: helpers.withMessage( + () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.FORMAT_ERROR'), + isValidSlug + ), + }, homePageLink: { shouldBeUrl }, }; @@ -71,9 +80,9 @@ const nameError = computed(() => v$.value.name.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.NAME.ERROR') : '' ); -const slugError = computed(() => - v$.value.slug.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR') : '' -); +const slugError = computed(() => { + return v$.value.slug.$errors[0]?.$message || ''; +}); const homePageLinkError = computed(() => v$.value.homePageLink.$error diff --git a/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue b/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue index d245656d0..70c30a241 100644 --- a/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue +++ b/app/javascript/dashboard/components-next/HelpCenter/PortalSwitcher/CreatePortalDialog.vue @@ -6,8 +6,9 @@ import { useAlert, useTrack } from 'dashboard/composables'; import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; import { convertToCategorySlug } from 'dashboard/helper/commons.js'; import { useVuelidate } from '@vuelidate/core'; -import { required, minLength } from '@vuelidate/validators'; +import { required, minLength, helpers } from '@vuelidate/validators'; import { buildPortalURL } from 'dashboard/helper/portalHelper'; +import { isValidSlug } from 'shared/helpers/Validators'; import Dialog from 'dashboard/components-next/dialog/Dialog.vue'; import Input from 'dashboard/components-next/input/Input.vue'; @@ -31,7 +32,16 @@ const state = reactive({ const rules = { name: { required, minLength: minLength(2) }, - slug: { required }, + slug: { + required: helpers.withMessage( + () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR'), + required + ), + isValidSlug: helpers.withMessage( + () => t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.FORMAT_ERROR'), + isValidSlug + ), + }, }; const v$ = useVuelidate(rules, state); @@ -40,9 +50,9 @@ const nameError = computed(() => v$.value.name.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.NAME.ERROR') : '' ); -const slugError = computed(() => - v$.value.slug.$error ? t('HELP_CENTER.CREATE_PORTAL_DIALOG.SLUG.ERROR') : '' -); +const slugError = computed(() => { + return v$.value.slug.$errors[0]?.$message || ''; +}); const isSubmitDisabled = computed(() => v$.value.$invalid); @@ -131,6 +141,7 @@ defineExpose({ dialogRef }); :message=" nameError || t('HELP_CENTER.CREATE_PORTAL_DIALOG.NAME.MESSAGE') " + @blur="v$.name.$touch()" /> diff --git a/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue b/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue index 0fefd76ee..1877e1630 100644 --- a/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue +++ b/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue @@ -33,6 +33,8 @@ const insertIntoRichEditor = computed(() => { ); }); +const hasEmptyMessageContent = computed(() => !props.message?.content); + const useCopilotResponse = () => { if (insertIntoRichEditor.value) { emitter.emit(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, props.message?.content); @@ -53,9 +55,17 @@ const useCopilotResponse = () => { />