diff --git a/Gemfile b/Gemfile
index a5068e765..c4989c538 100644
--- a/Gemfile
+++ b/Gemfile
@@ -84,6 +84,7 @@ gem 'barnes'
gem 'devise', '>= 4.9.4'
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
gem 'devise_token_auth', '>= 1.2.3'
+gem 'rails-i18n', '~> 7.0'
# two-factor authentication
gem 'devise-two-factor', '>= 5.0.0'
# authorization
diff --git a/Gemfile.lock b/Gemfile.lock
index b77e5880f..7d29e0b02 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -727,6 +727,9 @@ GEM
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
+ rails-i18n (7.0.10)
+ i18n (>= 0.7, < 2)
+ railties (>= 6.0.0, < 8)
railties (7.1.5.2)
actionpack (= 7.1.5.2)
activesupport (= 7.1.5.2)
@@ -1125,6 +1128,7 @@ DEPENDENCIES
rack-mini-profiler (>= 3.2.0)
rack-timeout
rails (~> 7.1)
+ rails-i18n (~> 7.0)
redis
redis-namespace
responders (>= 3.1.1)
diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb
index 532487a1b..5127ea612 100644
--- a/app/builders/account_builder.rb
+++ b/app/builders/account_builder.rb
@@ -44,7 +44,11 @@ class AccountBuilder
end
def create_account
- @account = Account.create!(name: account_name, locale: I18n.locale)
+ @account = Account.create!(
+ name: account_name,
+ locale: I18n.locale,
+ custom_attributes: { 'onboarding_step' => 'account_details' }
+ )
Current.account = @account
end
diff --git a/app/builders/agent_builder.rb b/app/builders/agent_builder.rb
index 2fe11cae0..d2715011c 100644
--- a/app/builders/agent_builder.rb
+++ b/app/builders/agent_builder.rb
@@ -29,8 +29,9 @@ class AgentBuilder
user = User.from_email(email)
return user if user
+ @name = email.split('@').first if @name.blank?
temp_password = "1!aA#{SecureRandom.alphanumeric(12)}"
- User.create!(email: email, name: name, password: temp_password, password_confirmation: temp_password)
+ User.create!(email: email, name: @name, password: temp_password, password_confirmation: temp_password)
end
# Checks if the user needs confirmation.
diff --git a/app/controllers/api/v1/accounts/articles/bulk_actions_controller.rb b/app/controllers/api/v1/accounts/articles/bulk_actions_controller.rb
new file mode 100644
index 000000000..b45c16828
--- /dev/null
+++ b/app/controllers/api/v1/accounts/articles/bulk_actions_controller.rb
@@ -0,0 +1,43 @@
+class Api::V1::Accounts::Articles::BulkActionsController < Api::V1::Accounts::BaseController
+ before_action :portal
+ before_action :check_authorization
+ before_action :set_articles, only: [:update_status, :delete_articles]
+
+ def translate
+ head :not_implemented
+ end
+
+ def update_status
+ return render_could_not_create_error(I18n.t('portals.articles.no_articles_found')) if @articles.none?
+ return render_could_not_create_error(I18n.t('portals.articles.invalid_status')) unless Article.statuses.key?(params[:status])
+
+ ActiveRecord::Base.transaction do
+ @articles.find_each { |article| article.update!(status: params[:status]) }
+ end
+ head :ok
+ rescue ActiveRecord::RecordInvalid => e
+ render_could_not_create_error(e.message)
+ end
+
+ def delete_articles
+ return render_could_not_create_error(I18n.t('portals.articles.no_articles_found')) if @articles.none?
+
+ @articles.destroy_all
+ head :ok
+ end
+
+ private
+
+ def portal
+ @portal ||= Current.account.portals.find_by!(slug: params[:portal_id])
+ end
+
+ def check_authorization
+ authorize(Article, :create?)
+ end
+
+ def set_articles
+ @articles = @portal.articles.where(id: params[:ids])
+ end
+end
+Api::V1::Accounts::Articles::BulkActionsController.prepend_mod_with('Api::V1::Accounts::Articles::BulkActionsController')
diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb
index dd5346bd6..eafda0fe2 100644
--- a/app/controllers/api/v1/accounts/contacts_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts_controller.rb
@@ -5,7 +5,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
sort_on :phone_number, type: :string
sort_on :last_activity_at, internal_name: :order_on_last_activity_at, type: :scope, scope_params: [:direction]
sort_on :created_at, internal_name: :order_on_created_at, type: :scope, scope_params: [:direction]
- sort_on :company, internal_name: :order_on_company_name, type: :scope, scope_params: [:direction]
+ sort_on :company_name, internal_name: :order_on_company_name, type: :scope, scope_params: [:direction]
sort_on :city, internal_name: :order_on_city, type: :scope, scope_params: [:direction]
sort_on :country, internal_name: :order_on_country_name, type: :scope, scope_params: [:direction]
diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb
index 972b244fa..ade83d8ec 100644
--- a/app/controllers/api/v1/accounts/portals_controller.rb
+++ b/app/controllers/api/v1/accounts/portals_controller.rb
@@ -61,9 +61,8 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
end
def process_attached_logo
- blob_id = params[:blob_id]
- blob = ActiveStorage::Blob.find_signed(blob_id)
- @portal.logo.attach(blob)
+ blob = ActiveStorage::Blob.find_signed(params[:blob_id].to_s)
+ @portal.logo.attach(blob) if blob
end
private
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 7176d6e1b..dddf3dd09 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -58,6 +58,7 @@ class Api::V1::AccountsController < Api::BaseController
@account.assign_attributes(account_params.slice(:name, :locale, :domain, :support_email))
@account.custom_attributes.merge!(custom_attributes_params)
@account.settings.merge!(settings_params)
+ @account.custom_attributes.delete('onboarding_step') if @account.custom_attributes['onboarding_step'] == 'account_details'
@account.custom_attributes['onboarding_step'] = 'invite_team' if @account.custom_attributes['onboarding_step'] == 'account_update'
@account.save!
end
@@ -71,9 +72,10 @@ class Api::V1::AccountsController < Api::BaseController
private
def enqueue_branding_enrichment
- return if account_params[:email].blank?
+ email = account_params[:email].presence || @user&.email
+ return if email.blank?
- Account::BrandingEnrichmentJob.perform_later(@account.id, account_params[:email])
+ Account::BrandingEnrichmentJob.perform_later(@account.id, email)
Redis::Alfred.set(format(Redis::Alfred::ACCOUNT_ONBOARDING_ENRICHMENT, account_id: @account.id), '1', ex: 30)
rescue StandardError => e
# Enrichment is optional — never let queue/Redis failures abort signup
@@ -109,7 +111,7 @@ class Api::V1::AccountsController < Api::BaseController
end
def custom_attributes_params
- params.permit(:industry, :company_size, :timezone)
+ params.permit(:industry, :company_size, :timezone, :referral_source, :user_role)
end
def settings_params
diff --git a/app/controllers/api/v1/profile/mfa_controller.rb b/app/controllers/api/v1/profile/mfa_controller.rb
index dd874f222..8480b64fb 100644
--- a/app/controllers/api/v1/profile/mfa_controller.rb
+++ b/app/controllers/api/v1/profile/mfa_controller.rb
@@ -2,8 +2,8 @@ class Api::V1::Profile::MfaController < Api::BaseController
before_action :check_mfa_feature_available
before_action :check_mfa_enabled, only: [:destroy, :backup_codes]
before_action :check_mfa_disabled, only: [:create, :verify]
- before_action :validate_otp, only: [:verify, :backup_codes, :destroy]
before_action :validate_password, only: [:destroy]
+ before_action :validate_otp, only: [:verify, :backup_codes, :destroy]
def show; end
@@ -48,7 +48,8 @@ class Api::V1::Profile::MfaController < Api::BaseController
def validate_otp
authenticated = Mfa::AuthenticationService.new(
user: current_user,
- otp_code: mfa_params[:otp_code]
+ otp_code: mfa_params[:otp_code],
+ backup_code: mfa_params[:backup_code]
).authenticate
return if authenticated
@@ -63,6 +64,6 @@ class Api::V1::Profile::MfaController < Api::BaseController
end
def mfa_params
- params.permit(:otp_code, :password)
+ params.permit(:otp_code, :backup_code, :password)
end
end
diff --git a/app/controllers/platform/api/v1/agent_bots_controller.rb b/app/controllers/platform/api/v1/agent_bots_controller.rb
index dd70a1ba5..594bb856e 100644
--- a/app/controllers/platform/api/v1/agent_bots_controller.rb
+++ b/app/controllers/platform/api/v1/agent_bots_controller.rb
@@ -3,7 +3,7 @@ class Platform::Api::V1::AgentBotsController < PlatformController
before_action :validate_platform_app_permissible, except: [:index, :create]
def index
- @resources = @platform_app.platform_app_permissibles.where(permissible_type: 'AgentBot').all
+ @resources = @platform_app.platform_app_permissibles.where(permissible_type: 'AgentBot').includes(:permissible)
end
def show; end
diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb
index fcc8b463d..67e8d953d 100644
--- a/app/helpers/email_helper.rb
+++ b/app/helpers/email_helper.rb
@@ -7,7 +7,7 @@ module EmailHelper
def render_email_html(content)
return '' if content.blank?
- ChatwootMarkdownRenderer.new(content).render_message.to_s
+ ChatwootMarkdownRenderer.new(content).render_message(hardbreaks: true).to_s
end
# Raise a standard error if any email address is invalid
diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue
index a706e2df5..988829407 100644
--- a/app/javascript/dashboard/App.vue
+++ b/app/javascript/dashboard/App.vue
@@ -59,7 +59,6 @@ export default {
isRTL: 'accounts/isRTL',
currentUser: 'getCurrentUser',
authUIFlags: 'getAuthUIFlags',
- accountUIFlags: 'accounts/getUIFlags',
}),
hideOnOnboardingView() {
return !isOnOnboardingView(this.$route);
@@ -107,8 +106,9 @@ export default {
this.$store.dispatch('setActiveAccount', {
accountId: this.currentAccountId,
});
+ const account = this.getAccount(this.currentAccountId);
const { locale, latest_chatwoot_version: latestChatwootVersion } =
- this.getAccount(this.currentAccountId);
+ account;
const { pubsub_token: pubsubToken } = this.currentUser || {};
// If user locale is set, use it; otherwise use account locale
this.setLocale(this.uiSettings?.locale || locale);
@@ -131,7 +131,7 @@ export default {
+ {{ subtitle }}
+
+ {{ $t('INBOX_MGMT.VOICE_CONFIGURATION.CREDENTIALS.DESCRIPTION') }}
+
+ {{ greeting }}
+
+
+
{{
$t('TEAMS_SETTINGS.AGENTS.SELECTED_COUNT', {
selected: selectedAgents.length,
diff --git a/app/javascript/dashboard/routes/dashboard/settings/teams/Create/AddAgents.vue b/app/javascript/dashboard/routes/dashboard/settings/teams/Create/AddAgents.vue
index ef1e14663..19a7a325d 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/teams/Create/AddAgents.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/teams/Create/AddAgents.vue
@@ -88,7 +88,7 @@ export default {
-