diff --git a/Gemfile b/Gemfile index a40b0f816..6b6813846 100644 --- a/Gemfile +++ b/Gemfile @@ -75,7 +75,7 @@ gem 'jwt' gem 'pundit' # super admin gem 'administrate', '>= 0.19.0' -gem 'administrate-field-active_storage' +gem 'administrate-field-active_storage', '>= 1.0.0' gem 'administrate-field-belongs_to_search' ##--- gems for pubsub service ---## @@ -109,14 +109,14 @@ gem 'elastic-apm', require: false gem 'newrelic_rpm', require: false gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false gem 'scout_apm', require: false -gem 'sentry-rails', '>= 5.13.0', require: false +gem 'sentry-rails', '>= 5.14.0', require: false gem 'sentry-ruby', require: false -gem 'sentry-sidekiq', '>= 5.13.0', require: false +gem 'sentry-sidekiq', '>= 5.14.0', require: false ##-- background job processing --## gem 'sidekiq', '>= 7.1.3' # We want cron jobs -gem 'sidekiq-cron', '>= 1.11.0' +gem 'sidekiq-cron', '>= 1.12.0' ##-- Push notification service --## gem 'fcm' @@ -198,7 +198,7 @@ group :development do gem 'squasher' # profiling - gem 'rack-mini-profiler', '>= 3.1.1', require: false + gem 'rack-mini-profiler', '>= 3.2.0', require: false gem 'stackprof' # Should install the associated chrome extension to view query logs gem 'meta_request' @@ -224,7 +224,7 @@ group :development, :test do gem 'byebug', platform: :mri gem 'climate_control' gem 'debug', '~> 1.8' - gem 'factory_bot_rails' + gem 'factory_bot_rails', '>= 6.4.2' gem 'listen' gem 'mock_redis' gem 'pry-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 8c782dd48..b224d09aa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -113,7 +113,7 @@ GEM kaminari (>= 1.0) sassc-rails (~> 2.1) selectize-rails (~> 0.6) - administrate-field-active_storage (0.4.2) + administrate-field-active_storage (1.0.0) administrate (>= 0.2.2) rails (>= 7.0) administrate-field-belongs_to_search (0.8.0) @@ -183,7 +183,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.3) + date (3.3.4) ddtrace (1.11.1) debase-ruby_core_source (>= 0.10.16, <= 3.2.0) libdatadog (~> 2.0.0.1.0) @@ -230,10 +230,10 @@ GEM facebook-messenger (2.0.1) httparty (~> 0.13, >= 0.13.7) rack (>= 1.4.5) - factory_bot (6.2.1) + factory_bot (6.4.2) activesupport (>= 5.0.0) - factory_bot_rails (6.2.0) - factory_bot (~> 6.2.0) + factory_bot_rails (6.4.2) + factory_bot (~> 6.4) railties (>= 5.0.0) faker (3.2.0) i18n (>= 1.8.11, < 2) @@ -256,7 +256,7 @@ GEM fcm (1.0.8) faraday (>= 1.0.0, < 3.0) googleauth (~> 1) - ffi (1.15.5) + ffi (1.16.3) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake @@ -440,7 +440,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -472,14 +472,14 @@ GEM activerecord (>= 5.2) net-http-persistent (4.0.2) connection_pool (~> 2.2) - net-imap (0.3.7) + net-imap (0.4.5) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.4.0) net-protocol netrc (0.11.0) newrelic-sidekiq-metrics (1.6.2) @@ -487,15 +487,15 @@ GEM sidekiq newrelic_rpm (9.6.0) base64 - nio4r (2.5.9) - nokogiri (1.15.4) + nio4r (2.6.0) + nokogiri (1.15.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.15.4-arm64-darwin) + nokogiri (1.15.5-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-darwin) + nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.15.5-x86_64-linux) racc (~> 1.4) numo-narray (0.9.2.1) oauth (1.1.0) @@ -566,7 +566,7 @@ GEM rack (< 4) rack-cors (2.0.1) rack (>= 2.0.0) - rack-mini-profiler (3.1.1) + rack-mini-profiler (3.2.0) rack (>= 1.2.0) rack-protection (3.0.6) rack @@ -610,7 +610,7 @@ GEM ffi (~> 1.0) redis (5.0.6) redis-client (>= 0.9.0) - redis-client (0.18.0) + redis-client (0.19.0) connection_pool redis-namespace (1.10.0) redis (>= 4) @@ -709,13 +709,13 @@ GEM activesupport (>= 4) selectize-rails (0.12.6) semantic_range (3.0.0) - sentry-rails (5.13.0) + sentry-rails (5.14.0) railties (>= 5.0) - sentry-ruby (~> 5.13.0) - sentry-ruby (5.13.0) + sentry-ruby (~> 5.14.0) + sentry-ruby (5.14.0) concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-sidekiq (5.13.0) - sentry-ruby (~> 5.13.0) + sentry-sidekiq (5.14.0) + sentry-ruby (~> 5.14.0) sidekiq (>= 3.0) sexp_processor (4.17.0) shoulda-matchers (5.3.0) @@ -725,7 +725,7 @@ GEM connection_pool (>= 2.3.0) rack (>= 2.2.4) redis-client (>= 0.14.0) - sidekiq-cron (1.11.0) + sidekiq-cron (1.12.0) fugit (~> 1.8) globalid (>= 1.0.1) sidekiq (>= 6) @@ -752,7 +752,7 @@ GEM spring-watcher-listen (2.1.0) listen (>= 2.7, < 4.0) spring (>= 4) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) @@ -766,11 +766,11 @@ GEM telephone_number (1.4.20) test-prof (1.2.1) thor (1.3.0) - tilt (2.2.0) + tilt (2.3.0) time_diff (0.3.0) activesupport i18n - timeout (0.4.0) + timeout (0.4.1) trailblazer-option (0.1.2) twilio-ruby (5.77.0) faraday (>= 0.9, < 3.0) @@ -841,7 +841,7 @@ DEPENDENCIES activerecord-import acts-as-taggable-on administrate (>= 0.19.0) - administrate-field-active_storage + administrate-field-active_storage (>= 1.0.0) administrate-field-belongs_to_search annotate attr_extras @@ -870,7 +870,7 @@ DEPENDENCIES elastic-apm email_reply_trimmer facebook-messenger - factory_bot_rails + factory_bot_rails (>= 6.4.2) faker fcm flag_shih_tzu @@ -918,7 +918,7 @@ DEPENDENCIES pundit rack-attack (>= 6.7.0) rack-cors - rack-mini-profiler (>= 3.1.1) + rack-mini-profiler (>= 3.2.0) rack-timeout rails (~> 7.0.8.0) redis @@ -935,12 +935,12 @@ DEPENDENCIES scout_apm scss_lint seed_dump - sentry-rails (>= 5.13.0) + sentry-rails (>= 5.14.0) sentry-ruby - sentry-sidekiq (>= 5.13.0) + sentry-sidekiq (>= 5.14.0) shoulda-matchers sidekiq (>= 7.1.3) - sidekiq-cron (>= 1.11.0) + sidekiq-cron (>= 1.12.0) simplecov (= 0.17.1) slack-ruby-client (~> 2.2.0) spring diff --git a/VERSION_CW b/VERSION_CW index fd2a01863..bea438e9a 100644 --- a/VERSION_CW +++ b/VERSION_CW @@ -1 +1 @@ -3.1.0 +3.3.1 diff --git a/VERSION_CWCTL b/VERSION_CWCTL index e70b4523a..24ba9a38d 100644 --- a/VERSION_CWCTL +++ b/VERSION_CWCTL @@ -1 +1 @@ -2.6.0 +2.7.0 diff --git a/app/assets/stylesheets/administrate/application.scss b/app/assets/stylesheets/administrate/application.scss index b8c5df794..66ac42405 100644 --- a/app/assets/stylesheets/administrate/application.scss +++ b/app/assets/stylesheets/administrate/application.scss @@ -25,7 +25,6 @@ @import 'components/flashes'; @import 'components/form-actions'; @import 'components/main-content'; -@import 'components/navigation'; @import 'components/pagination'; @import 'components/search'; @import 'components/reports'; diff --git a/app/assets/stylesheets/administrate/base/_layout.scss b/app/assets/stylesheets/administrate/base/_layout.scss index c4c081a82..c2415122b 100644 --- a/app/assets/stylesheets/administrate/base/_layout.scss +++ b/app/assets/stylesheets/administrate/base/_layout.scss @@ -1,7 +1,7 @@ html { background-color: $color-white; box-sizing: border-box; - font-size: 10px; + font-size: 16px; -webkit-font-smoothing: antialiased; } diff --git a/app/assets/stylesheets/administrate/components/_attributes.scss b/app/assets/stylesheets/administrate/components/_attributes.scss index 2b2936650..af6c27ac3 100644 --- a/app/assets/stylesheets/administrate/components/_attributes.scss +++ b/app/assets/stylesheets/administrate/components/_attributes.scss @@ -16,8 +16,8 @@ .attribute-data { float: left; margin-bottom: $base-spacing; - margin-left: 2rem; - width: calc(84% - 1rem); + margin-left: 1.25rem; + width: calc(84% - 0.625rem); } .attribute--nested { diff --git a/app/assets/stylesheets/administrate/components/_field-unit.scss b/app/assets/stylesheets/administrate/components/_field-unit.scss index 856c1872c..91b5153e4 100644 --- a/app/assets/stylesheets/administrate/components/_field-unit.scss +++ b/app/assets/stylesheets/administrate/components/_field-unit.scss @@ -9,22 +9,22 @@ .field-unit__label { float: left; - margin-left: 1rem; + margin-left: 0.625rem; text-align: right; - width: calc(15% - 1rem); + width: calc(15% - 0.625rem); } .field-unit__field { float: left; - margin-left: 2rem; - max-width: 50rem; + margin-left: 1.25rem; + max-width: 31.15rem; width: 100%; } .field-unit--nested { border: $base-border; margin-left: 7.5%; - max-width: 60rem; + max-width: 37.5rem; padding: $small-spacing; width: 100%; diff --git a/app/assets/stylesheets/administrate/components/_form-actions.scss b/app/assets/stylesheets/administrate/components/_form-actions.scss index d87d17435..05ec352f2 100644 --- a/app/assets/stylesheets/administrate/components/_form-actions.scss +++ b/app/assets/stylesheets/administrate/components/_form-actions.scss @@ -1,3 +1,3 @@ .form-actions { - margin-left: calc(15% + 2rem); + margin-left: calc(15% + 1.25rem); } diff --git a/app/assets/stylesheets/administrate/components/_main-content.scss b/app/assets/stylesheets/administrate/components/_main-content.scss index 590bb0985..ab503dba8 100644 --- a/app/assets/stylesheets/administrate/components/_main-content.scss +++ b/app/assets/stylesheets/administrate/components/_main-content.scss @@ -13,6 +13,10 @@ table { font-size: $font-size-small; } + + form { + margin-top: $space-two; + } } .main-content__header { @@ -20,7 +24,7 @@ background-color: $color-white; border-bottom: 1px solid $color-border; display: flex; - min-height: 5.6rem; + min-height: 3.5rem; padding: $space-small $space-normal; } diff --git a/app/assets/stylesheets/administrate/components/_navigation.scss b/app/assets/stylesheets/administrate/components/_navigation.scss deleted file mode 100644 index 4ffc42190..000000000 --- a/app/assets/stylesheets/administrate/components/_navigation.scss +++ /dev/null @@ -1,88 +0,0 @@ -.logo-brand { - margin-bottom: $space-normal; - padding: $space-normal $space-smaller $space-small; - text-align: left; - - img { - margin-bottom: $space-smaller; - max-height: 3rem; - } -} - -.navigation { - background: $white; - border-right: 1px solid $color-border; - display: flex; - flex-direction: column; - font-size: $font-size-default; - font-weight: $font-weight-medium; - height: 100%; - justify-content: flex-start; - left: 0; - margin: 0; - overflow: auto; - padding: $space-normal; - position: fixed; - top: 0; - width: 21rem; - z-index: 1023; - - li { - align-items: center; - display: flex; - font-size: $font-size-small; - - a { - color: $color-gray; - text-decoration: none; - } - - i { - min-width: $space-medium; - } - } - - hr { - margin: $space-slab; - } -} - -.navigation__link { - background-color: transparent; - color: $color-gray; - display: block; - line-height: 1; - margin-bottom: $space-smaller; - padding: $space-small; - - &:hover { - color: $blue; - - a { - color: $blue; - } - } - - - &.navigation__link--active { - background-color: $color-background; - border-radius: $base-border-radius; - color: $blue; - - a { - color: $blue; - } - } -} - -.logout { - bottom: $space-normal; - left: $space-normal; - position: fixed; -} - -.app-version { - color: $color-gray; - font-size: $font-size-small; - padding-top: $space-smaller; -} diff --git a/app/assets/stylesheets/administrate/components/_search.scss b/app/assets/stylesheets/administrate/components/_search.scss index f3a259618..bd5c2eece 100644 --- a/app/assets/stylesheets/administrate/components/_search.scss +++ b/app/assets/stylesheets/administrate/components/_search.scss @@ -1,7 +1,7 @@ .search { margin-left: auto; - margin-right: 2rem; - max-width: 44rem; + margin-right: 1.25rem; + max-width: 27.5rem; position: relative; width: 100%; } diff --git a/app/assets/stylesheets/administrate/library/_variables.scss b/app/assets/stylesheets/administrate/library/_variables.scss index 086428556..2e424fb06 100644 --- a/app/assets/stylesheets/administrate/library/_variables.scss +++ b/app/assets/stylesheets/administrate/library/_variables.scss @@ -1,10 +1,10 @@ // Typography -$base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", +$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif !default; $heading-font-family: $base-font-family !default; -$base-font-size: 14px !default; +$base-font-size: 16px !default; $base-line-height: 1.5 !default; $heading-line-height: 1.2 !default; diff --git a/app/assets/stylesheets/administrate/utilities/_variables.scss b/app/assets/stylesheets/administrate/utilities/_variables.scss index 818f96e4c..3a1129c41 100644 --- a/app/assets/stylesheets/administrate/utilities/_variables.scss +++ b/app/assets/stylesheets/administrate/utilities/_variables.scss @@ -1,30 +1,30 @@ // Font sizes -$font-size-nano: 0.8rem; -$font-size-micro: 1.0rem; -$font-size-mini: 1.2rem; -$font-size-small: 1.4rem; -$font-size-default: 1.6rem; -$font-size-medium: 1.8rem; -$font-size-large: 2.2rem; -$font-size-big: 2.4rem; -$font-size-bigger: 3.0rem; -$font-size-mega: 3.4rem; -$font-size-giga: 4.0rem; +$font-size-nano: 0.5rem; +$font-size-micro: 0.675rem; +$font-size-mini: 0.75rem; +$font-size-small: 0.875rem; +$font-size-default: 1rem; +$font-size-medium: 1.125rem; +$font-size-large: 1.375rem; +$font-size-big: 1.5rem; +$font-size-bigger: 1.75rem; +$font-size-mega: 2.125rem; +$font-size-giga: 2.5rem; // spaces $zero: 0; -$space-micro: 0.2rem; -$space-smaller: 0.4rem; -$space-small: 0.8rem; -$space-one: 1rem; -$space-slab: 1.2rem; -$space-normal: 1.6rem; -$space-two: 2.0rem; -$space-medium: 2.4rem; -$space-large: 3.2rem; -$space-larger: 4.8rem; -$space-jumbo: 6.4rem; -$space-mega: 10.0rem; +$space-micro: 0.125rem; +$space-smaller: 0.25rem; +$space-small: 0.5rem; +$space-one: 0.675rem; +$space-slab: 0.75rem; +$space-normal: 1rem; +$space-two: 1.25rem; +$space-medium: 1.5rem; +$space-large: 2rem; +$space-larger: 3rem; +$space-jumbo: 4rem; +$space-mega: 6.25rem; // font-weight $font-weight-feather: 100; diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb index 3efb184b9..fec298bce 100644 --- a/app/builders/messages/facebook/message_builder.rb +++ b/app/builders/messages/facebook/message_builder.rb @@ -25,7 +25,9 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder build_contact_inbox build_message end - rescue Koala::Facebook::AuthenticationError + rescue Koala::Facebook::AuthenticationError => e + Rails.logger.warn("Facebook authentication error for inbox: #{@inbox.id} with error: #{e.message}") + Rails.logger.error e @inbox.channel.authorization_error! rescue StandardError => e ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception @@ -108,11 +110,15 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder } end + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength def contact_params begin k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook? result = k.get_object(@sender_id) || {} - rescue Koala::Facebook::AuthenticationError + rescue Koala::Facebook::AuthenticationError => e + Rails.logger.warn("Facebook authentication error for inbox: #{@inbox.id} with error: #{e.message}") + Rails.logger.error e @inbox.channel.authorization_error! raise rescue Koala::Facebook::ClientError => e @@ -130,4 +136,6 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder end process_contact_params_result(result) end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength end diff --git a/app/builders/messages/instagram/message_builder.rb b/app/builders/messages/instagram/message_builder.rb index 5b2243906..5610e0671 100644 --- a/app/builders/messages/instagram/message_builder.rb +++ b/app/builders/messages/instagram/message_builder.rb @@ -20,7 +20,9 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder ActiveRecord::Base.transaction do build_message end - rescue Koala::Facebook::AuthenticationError + rescue Koala::Facebook::AuthenticationError => e + Rails.logger.warn("Instagram authentication error for inbox: #{@inbox.id} with error: #{e.message}") + Rails.logger.error e @inbox.channel.authorization_error! raise rescue StandardError => e diff --git a/app/builders/messages/message_builder.rb b/app/builders/messages/message_builder.rb index 0ec73edc3..e1087b19f 100644 --- a/app/builders/messages/message_builder.rb +++ b/app/builders/messages/message_builder.rb @@ -149,7 +149,8 @@ class Messages::MessageBuilder content_type: @params[:content_type], items: @items, in_reply_to: @in_reply_to, - echo_id: @params[:echo_id] + echo_id: @params[:echo_id], + source_id: @params[:source_id] }.merge(external_created_at).merge(automation_rule_id).merge(campaign_id).merge(template_params) end end diff --git a/app/builders/notification_builder.rb b/app/builders/notification_builder.rb index 6d2096233..8efe44fd6 100644 --- a/app/builders/notification_builder.rb +++ b/app/builders/notification_builder.rb @@ -1,5 +1,5 @@ class NotificationBuilder - pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!] + pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!, :secondary_actor] def perform return unless user_subscribed_to_notification? @@ -9,7 +9,7 @@ class NotificationBuilder private - def secondary_actor + def current_user Current.user end @@ -29,7 +29,8 @@ class NotificationBuilder notification_type: notification_type, account: account, primary_actor: primary_actor, - secondary_actor: secondary_actor + # secondary_actor is secondary_actor if present, else current_user + secondary_actor: secondary_actor || current_user ) end end diff --git a/app/controllers/api/v1/accounts/agent_bots_controller.rb b/app/controllers/api/v1/accounts/agent_bots_controller.rb index efb48c5c6..43bce17bc 100644 --- a/app/controllers/api/v1/accounts/agent_bots_controller.rb +++ b/app/controllers/api/v1/accounts/agent_bots_controller.rb @@ -10,11 +10,18 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController def show; end def create - @agent_bot = Current.account.agent_bots.create!(permitted_params) + @agent_bot = Current.account.agent_bots.create!(permitted_params.except(:avatar_url)) + process_avatar_from_url end def update - @agent_bot.update!(permitted_params) + @agent_bot.update!(permitted_params.except(:avatar_url)) + process_avatar_from_url + end + + def avatar + @agent_bot.avatar.purge if @agent_bot.avatar.attached? + @agent_bot end def destroy @@ -30,6 +37,10 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController end def permitted_params - params.permit(:name, :description, :outgoing_url, :bot_type, bot_config: [:csml_content]) + params.permit(:name, :description, :outgoing_url, :avatar, :avatar_url, :bot_type, bot_config: [:csml_content]) + end + + def process_avatar_from_url + ::Avatar::AvatarFromUrlJob.perform_later(@agent_bot, params[:avatar_url]) if params[:avatar_url].present? end end diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index bec00be96..f424f3b66 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -83,14 +83,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController @contact = Current.account.contacts.new(permitted_params.except(:avatar_url)) @contact.save! @contact_inbox = build_contact_inbox - process_avatar + process_avatar_from_url end end def update @contact.assign_attributes(contact_update_params) @contact.save! - process_avatar if permitted_params[:avatar].present? || permitted_params[:avatar_url].present? + process_avatar_from_url end def destroy @@ -174,7 +174,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id]) end - def process_avatar + def process_avatar_from_url ::Avatar::AvatarFromUrlJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present? end diff --git a/app/controllers/api/v1/accounts/conversations/assignments_controller.rb b/app/controllers/api/v1/accounts/conversations/assignments_controller.rb index 68ff2e67d..1fb2095e3 100644 --- a/app/controllers/api/v1/accounts/conversations/assignments_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/assignments_controller.rb @@ -14,7 +14,8 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account def set_agent @agent = Current.account.users.find_by(id: params[:assignee_id]) - @conversation.update_assignee(@agent) + @conversation.assignee = @agent + @conversation.save! render_agent end diff --git a/app/controllers/api/v1/accounts/conversations/messages_controller.rb b/app/controllers/api/v1/accounts/conversations/messages_controller.rb index 319c6763f..d34561e36 100644 --- a/app/controllers/api/v1/accounts/conversations/messages_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/messages_controller.rb @@ -18,6 +18,15 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts:: end end + def retry + return if message.blank? + + message.update!(status: :sent, content_attributes: {}) + ::SendReplyJob.perform_later(message.id) + rescue StandardError => e + render_could_not_create_error(e.message) + end + def translate return head :ok if already_translated_content_available? diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index eb3187d54..a3d3ad645 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -110,8 +110,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def assign_conversation - @agent = Current.account.users.find(current_user.id) - @conversation.update_assignee(@agent) + @conversation.assignee = current_user + @conversation.save! end def conversation diff --git a/app/controllers/api/v1/accounts/notifications_controller.rb b/app/controllers/api/v1/accounts/notifications_controller.rb index ef08be6d9..0d8cf6a47 100644 --- a/app/controllers/api/v1/accounts/notifications_controller.rb +++ b/app/controllers/api/v1/accounts/notifications_controller.rb @@ -1,7 +1,8 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController RESULTS_PER_PAGE = 15 + include DateRangeHelper - before_action :fetch_notification, only: [:update] + before_action :fetch_notification, only: [:update, :destroy, :snooze] before_action :set_primary_actor, only: [:read_all] before_action :set_current_page, only: [:index] @@ -28,11 +29,21 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro render json: @notification end + def destroy + @notification.destroy + head :ok + end + def unread_count @unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count render json: @unread_count end + def snooze + @notification.update(snoozed_until: parse_date_time(params[:snoozed_until].to_s)) if params[:snoozed_until] + render json: @notification + end + private def set_primary_actor diff --git a/app/controllers/platform/api/v1/agent_bots_controller.rb b/app/controllers/platform/api/v1/agent_bots_controller.rb index 138052b77..dd70a1ba5 100644 --- a/app/controllers/platform/api/v1/agent_bots_controller.rb +++ b/app/controllers/platform/api/v1/agent_bots_controller.rb @@ -9,13 +9,15 @@ class Platform::Api::V1::AgentBotsController < PlatformController def show; end def create - @resource = AgentBot.new(agent_bot_params) + @resource = AgentBot.new(agent_bot_params.except(:avatar_url)) @resource.save! + process_avatar_from_url @platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource) end def update - @resource.update!(agent_bot_params) + @resource.update!(agent_bot_params.except(:avatar_url)) + process_avatar_from_url end def destroy @@ -23,6 +25,11 @@ class Platform::Api::V1::AgentBotsController < PlatformController head :ok end + def avatar + @resource.avatar.purge if @resource.avatar.attached? + @resource + end + private def set_resource @@ -30,6 +37,10 @@ class Platform::Api::V1::AgentBotsController < PlatformController end def agent_bot_params - params.permit(:name, :description, :account_id, :outgoing_url) + params.permit(:name, :description, :account_id, :outgoing_url, :avatar, :avatar_url) + end + + def process_avatar_from_url + ::Avatar::AvatarFromUrlJob.perform_later(@resource, params[:avatar_url]) if params[:avatar_url].present? end end diff --git a/app/controllers/public/api/v1/portals/base_controller.rb b/app/controllers/public/api/v1/portals/base_controller.rb index df9526a69..e003a62ba 100644 --- a/app/controllers/public/api/v1/portals/base_controller.rb +++ b/app/controllers/public/api/v1/portals/base_controller.rb @@ -11,11 +11,7 @@ class Public::Api::V1::Portals::BaseController < PublicController end def set_color_scheme - @theme = if %w[dark light].include?(params[:theme]) - params[:theme] - else - 'system' - end + @theme_from_params = params[:theme] if %w[dark light].include?(params[:theme]) end def portal diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index 2899a7def..ce06879c5 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -1,21 +1,38 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController + before_action :set_config + before_action :allowed_configs def show - @allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET] # ref: https://github.com/rubocop/rubocop/issues/7767 # rubocop:disable Style/HashTransformValues - @fb_config = InstallationConfig.where(name: @allowed_configs) - .pluck(:name, :serialized_value) - .map { |name, serialized_value| [name, serialized_value['value']] } - .to_h + @app_config = InstallationConfig.where(name: @allowed_configs) + .pluck(:name, :serialized_value) + .map { |name, serialized_value| [name, serialized_value['value']] } + .to_h # rubocop:enable Style/HashTransformValues end def create params['app_config'].each do |key, value| + next unless @allowed_configs.include?(key) + i = InstallationConfig.where(name: key).first_or_create(value: value, locked: false) i.value = value i.save! end - redirect_to super_admin_app_config_url + # rubocop:disable Rails/I18nLocaleTexts + redirect_to super_admin_settings_path, notice: 'App Configs updated successfully' + # rubocop:enable Rails/I18nLocaleTexts + end + + private + + def set_config + @config = params[:config] + end + + def allowed_configs + @allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET] end end + +SuperAdmin::AppConfigsController.prepend_mod_with('SuperAdmin::AppConfigsController') diff --git a/app/controllers/super_admin/application_controller.rb b/app/controllers/super_admin/application_controller.rb index 69b61b913..3b98a6e21 100644 --- a/app/controllers/super_admin/application_controller.rb +++ b/app/controllers/super_admin/application_controller.rb @@ -20,4 +20,13 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController params.fetch(resource_name, {}).fetch(:direction, 'desc') ) end + + private + + def invalid_action_perfomed + # rubocop:disable Rails/I18nLocaleTexts + flash[:error] = 'Invalid action performed' + # rubocop:enable Rails/I18nLocaleTexts + redirect_back(fallback_location: root_path) + end end diff --git a/app/controllers/super_admin/devise/sessions_controller.rb b/app/controllers/super_admin/devise/sessions_controller.rb index b5e50d177..96389cfb5 100644 --- a/app/controllers/super_admin/devise/sessions_controller.rb +++ b/app/controllers/super_admin/devise/sessions_controller.rb @@ -27,7 +27,8 @@ class SuperAdmin::Devise::SessionsController < Devise::SessionsController true rescue StandardError => e - @error_message = e.message + Rails.logger.error e.message + @error_message = 'Invalid credentials. Please try again.' false end end diff --git a/app/controllers/super_admin/installation_configs_controller.rb b/app/controllers/super_admin/installation_configs_controller.rb index 36a45f707..b1f15b518 100644 --- a/app/controllers/super_admin/installation_configs_controller.rb +++ b/app/controllers/super_admin/installation_configs_controller.rb @@ -1,4 +1,5 @@ class SuperAdmin::InstallationConfigsController < SuperAdmin::ApplicationController + rescue_from ActiveRecord::RecordNotUnique, :with => :invalid_action_perfomed # Overwrite any of the RESTful controller actions to implement custom behavior # For example, you may want to send an email after a foo is updated. # diff --git a/app/controllers/super_admin/settings_controller.rb b/app/controllers/super_admin/settings_controller.rb new file mode 100644 index 000000000..685e6a8bd --- /dev/null +++ b/app/controllers/super_admin/settings_controller.rb @@ -0,0 +1,10 @@ +class SuperAdmin::SettingsController < SuperAdmin::ApplicationController + def show; end + + def refresh + Internal::CheckNewVersionsJob.perform_now + # rubocop:disable Rails/I18nLocaleTexts + redirect_to super_admin_settings_path, notice: 'Instance status refreshed' + # rubocop:enable Rails/I18nLocaleTexts + end +end diff --git a/app/dashboards/access_token_dashboard.rb b/app/dashboards/access_token_dashboard.rb index d3f05a799..927aecadd 100644 --- a/app/dashboards/access_token_dashboard.rb +++ b/app/dashboards/access_token_dashboard.rb @@ -30,11 +30,7 @@ class AccessTokenDashboard < Administrate::BaseDashboard # SHOW_PAGE_ATTRIBUTES # an array of attributes that will be displayed on the model's show page. SHOW_PAGE_ATTRIBUTES = %i[ - owner - id token - created_at - updated_at ].freeze # FORM_ATTRIBUTES diff --git a/app/dashboards/agent_bot_dashboard.rb b/app/dashboards/agent_bot_dashboard.rb index baeb6e814..a253b2406 100644 --- a/app/dashboards/agent_bot_dashboard.rb +++ b/app/dashboards/agent_bot_dashboard.rb @@ -46,6 +46,7 @@ class AgentBotDashboard < Administrate::BaseDashboard name description outgoing_url + access_token ].freeze # FORM_ATTRIBUTES diff --git a/app/dashboards/platform_app_dashboard.rb b/app/dashboards/platform_app_dashboard.rb index f5ed564ef..80fc7c5cd 100644 --- a/app/dashboards/platform_app_dashboard.rb +++ b/app/dashboards/platform_app_dashboard.rb @@ -32,6 +32,7 @@ class PlatformAppDashboard < Administrate::BaseDashboard name created_at updated_at + access_token ].freeze # FORM_ATTRIBUTES diff --git a/app/dashboards/user_dashboard.rb b/app/dashboards/user_dashboard.rb index e00d06a72..6b2129eed 100644 --- a/app/dashboards/user_dashboard.rb +++ b/app/dashboards/user_dashboard.rb @@ -36,7 +36,8 @@ class UserDashboard < Administrate::BaseDashboard updated_at: Field::DateTime, pubsub_token: Field::String, type: Field::Select.with_options(collection: [nil, 'SuperAdmin']), - accounts: CountField + accounts: CountField, + access_token: Field::HasOne }.freeze # COLLECTION_ATTRIBUTES @@ -67,6 +68,7 @@ class UserDashboard < Administrate::BaseDashboard updated_at confirmed_at account_users + access_token ].freeze # FORM_ATTRIBUTES diff --git a/app/drops/contact_drop.rb b/app/drops/contact_drop.rb index 6611ecdc3..1d450adb7 100644 --- a/app/drops/contact_drop.rb +++ b/app/drops/contact_drop.rb @@ -18,4 +18,9 @@ class ContactDrop < BaseDrop def last_name @obj.try(:name).try(:split).try(:last).try(:capitalize) if @obj.try(:name).try(:split).try(:size) > 1 end + + def custom_attribute + custom_attributes = @obj.try(:custom_attributes) || {} + custom_attributes.transform_keys(&:to_s) + end end diff --git a/app/drops/conversation_drop.rb b/app/drops/conversation_drop.rb index d77e150c2..d62642885 100644 --- a/app/drops/conversation_drop.rb +++ b/app/drops/conversation_drop.rb @@ -19,6 +19,11 @@ class ConversationDrop < BaseDrop end end + def custom_attribute + custom_attributes = @obj.try(:custom_attributes) || {} + custom_attributes.transform_keys(&:to_s) + end + private def message_sender_name(sender) diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index cbbd60caa..0cc5e52d8 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -3,13 +3,21 @@ class ConversationFinder DEFAULT_STATUS = 'open'.freeze SORT_OPTIONS = { - latest: 'latest', - sort_on_created_at: 'sort_on_created_at', - last_user_message_at: 'last_user_message_at', - sort_on_priority: 'sort_on_priority', - sort_on_waiting_since: 'sort_on_waiting_since' - }.with_indifferent_access + 'last_activity_at_asc' => %w[sort_on_last_activity_at asc], + 'last_activity_at_desc' => %w[sort_on_last_activity_at desc], + 'created_at_asc' => %w[sort_on_created_at asc], + 'created_at_desc' => %w[sort_on_created_at desc], + 'priority_asc' => %w[sort_on_priority asc], + 'priority_desc' => %w[sort_on_priority desc], + 'waiting_since_asc' => %w[sort_on_waiting_since asc], + 'waiting_since_desc' => %w[sort_on_waiting_since desc], + # To be removed in v3.5.0 + 'latest' => %w[sort_on_last_activity_at desc], + 'sort_on_created_at' => %w[sort_on_created_at asc], + 'sort_on_priority' => %w[sort_on_priority desc], + 'sort_on_waiting_since' => %w[sort_on_waiting_since asc] + }.with_indifferent_access # assumptions # inbox_id if not given, take from all conversations, else specific to inbox # assignee_type if not given, take 'all' @@ -159,7 +167,8 @@ class ConversationFinder @conversations = @conversations.includes( :taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox ) - sort_by = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['latest'] - @conversations.send(sort_by).page(current_page) + + sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc'] + @conversations.send(sort_by, sort_order).page(current_page).per(ENV.fetch('CONVERSATION_RESULTS_PER_PAGE', '25').to_i) end end diff --git a/app/helpers/portal_helper.rb b/app/helpers/portal_helper.rb index 72d4eeda1..342bb62aa 100644 --- a/app/helpers/portal_helper.rb +++ b/app/helpers/portal_helper.rb @@ -6,7 +6,17 @@ module PortalHelper def generate_portal_bg(portal_color, theme) bg_image = theme == 'dark' ? 'hexagon-dark.svg' : 'hexagon-light.svg' - "background: url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}" + "url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}" + end + + def generate_gradient_to_bottom(theme) + base_color = theme == 'dark' ? '#151718' : 'white' + "linear-gradient(to bottom, transparent, #{base_color})" + end + + def generate_portal_hover_color(portal_color, theme) + base_color = theme == 'dark' ? '#1B1B1B' : '#F9F9F9' + "color-mix(in srgb, #{portal_color} 5%, #{base_color})" end def language_name(locale) @@ -14,35 +24,48 @@ module PortalHelper language_map[locale] || locale end - def get_theme_names(theme) - if theme == 'light' - I18n.t('public_portal.header.appearance.light') - elsif theme == 'dark' - I18n.t('public_portal.header.appearance.dark') + def theme_query_string(theme) + theme.present? && theme != 'system' ? "?theme=#{theme}" : '' + end + + def generate_home_link(portal_slug, portal_locale, theme, is_plain_layout_enabled) + if is_plain_layout_enabled + "/hc/#{portal_slug}/#{portal_locale}#{theme_query_string(theme)}" else - I18n.t('public_portal.header.appearance.system') + "/hc/#{portal_slug}/#{portal_locale}" end end - def get_theme_icon(theme) - if theme == 'light' - 'icons/sun' - elsif theme == 'dark' - 'icons/moon' + def generate_category_link(params) + portal_slug = params[:portal_slug] + category_locale = params[:category_locale] + category_slug = params[:category_slug] + theme = params[:theme] + is_plain_layout_enabled = params[:is_plain_layout_enabled] + + if is_plain_layout_enabled + "/hc/#{portal_slug}/#{category_locale}/categories/#{category_slug}#{theme_query_string(theme)}" else - 'icons/monitor' + "/hc/#{portal_slug}/#{category_locale}/categories/#{category_slug}" end end - def generate_gradient_to_bottom(theme) - "background-image: linear-gradient(to bottom, transparent, #{theme == 'dark' ? '#151718' : 'white'})" - end - - def generate_article_link(portal_slug, article_slug, theme) - "/hc/#{portal_slug}/articles/#{article_slug}#{theme.present? && theme != 'system' ? "?theme=#{theme}" : ''}" + def generate_article_link(portal_slug, article_slug, theme, is_plain_layout_enabled) + if is_plain_layout_enabled + "/hc/#{portal_slug}/articles/#{article_slug}#{theme_query_string(theme)}" + else + "/hc/#{portal_slug}/articles/#{article_slug}" + end end def render_category_content(content) ChatwootMarkdownRenderer.new(content).render_markdown_to_plain_text end + + def thumbnail_bg_color(username) + colors = ['#6D95BA', '#A4C3C3', '#E19191'] + return colors.sample if username.blank? + + colors[username.length % colors.size] + end end diff --git a/app/javascript/dashboard/api/inbox/message.js b/app/javascript/dashboard/api/inbox/message.js index 6cb885e3d..8f294a0ee 100644 --- a/app/javascript/dashboard/api/inbox/message.js +++ b/app/javascript/dashboard/api/inbox/message.js @@ -86,6 +86,12 @@ class MessageApi extends ApiClient { return axios.delete(`${this.url}/${conversationID}/messages/${messageId}`); } + retry(conversationID, messageId) { + return axios.post( + `${this.url}/${conversationID}/messages/${messageId}/retry` + ); + } + getPreviousMessages({ conversationId, after, before }) { const params = { before }; if (after && Number(after) !== Number(before)) { diff --git a/app/javascript/dashboard/assets/images/typing.gif b/app/javascript/dashboard/assets/images/typing.gif index dd9b1ca2b..b288d2559 100644 Binary files a/app/javascript/dashboard/assets/images/typing.gif and b/app/javascript/dashboard/assets/images/typing.gif differ diff --git a/app/javascript/dashboard/assets/scss/super_admin/index.scss b/app/javascript/dashboard/assets/scss/super_admin/index.scss index f95f1303f..91f7835d0 100644 --- a/app/javascript/dashboard/assets/scss/super_admin/index.scss +++ b/app/javascript/dashboard/assets/scss/super_admin/index.scss @@ -1,39 +1,8 @@ -@import '../variables'; +@import 'shared/assets/fonts/inter'; +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; -.superadmin-body { - background: var(--color-background); - - .hero--title { - font-size: var(--font-size-mega); - font-weight: var(--font-weight-light); - margin-top: var(--space-large); - } - - .update-subscription--checkbox { - display: flex; - - input { - line-height: 1.5; - margin-right: var(--space-one); - margin-top: var(--space-smaller); - } - - label { - font-size: var(--font-size-small); - line-height: 1.5; - margin-bottom: var(--space-normal); - } - } +body { + font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; } - -.alert-box { - background-color: var(--r-500); - border-radius: 5px; - color: var(--color-white); - font-size: 14px; - margin-bottom: 14px; - padding: 10px; - text-align: center; -} - - diff --git a/app/javascript/dashboard/assets/scss/super_admin/pages.scss b/app/javascript/dashboard/assets/scss/super_admin/pages.scss deleted file mode 100644 index a33da2693..000000000 --- a/app/javascript/dashboard/assets/scss/super_admin/pages.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import 'shared/assets/fonts/plus-jakarta'; -@import '../variables'; -@import '~shared/assets/stylesheets/ionicons'; diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 194bf5e5d..f3e901d37 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -126,49 +126,33 @@ @assign-team="onAssignTeamsForBulk" />
-
- -
-
- -
- - - {{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }} - - -

- {{ $t('CHAT_LIST.EOF') }} -

+ +
import { mapGetters } from 'vuex'; +import VirtualList from 'vue-virtual-scroll-list'; import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue'; import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue'; import ChatTypeTabs from './widgets/ChatTypeTabs.vue'; -import ConversationCard from './widgets/conversation/ConversationCard.vue'; +import ConversationItem from './ConversationItem.vue'; import timeMixin from '../mixins/time'; import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import conversationMixin from '../mixins/conversations'; @@ -222,16 +207,20 @@ import { isOnUnattendedView, } from '../store/modules/conversations/helpers/actionHelpers'; import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events'; +import IntersectionObserver from './IntersectionObserver.vue'; export default { components: { AddCustomViews, ChatTypeTabs, - ConversationCard, + // eslint-disable-next-line vue/no-unused-components + ConversationItem, ConversationAdvancedFilter, DeleteCustomViews, ConversationBulkActions, ConversationBasicFilter, + IntersectionObserver, + VirtualList, }, mixins: [ timeMixin, @@ -241,6 +230,20 @@ export default { filterMixin, uiSettingsMixin, ], + provide() { + return { + // Actions to be performed on virtual list item and context menu. + selectConversation: this.selectConversation, + deSelectConversation: this.deSelectConversation, + assignAgent: this.onAssignAgent, + assignTeam: this.onAssignTeam, + assignLabels: this.onAssignLabels, + updateConversationStatus: this.toggleConversationStatus, + toggleContextMenu: this.onContextMenuToggle, + markAsUnread: this.markAsUnread, + assignPriority: this.assignPriority, + }; + }, props: { conversationInbox: { type: [String, Number], @@ -275,7 +278,7 @@ export default { return { activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME, activeStatus: wootConstants.STATUS_TYPE.OPEN, - activeSortBy: wootConstants.SORT_BY_TYPE.LATEST, + activeSortBy: wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC, showAdvancedFilters: false, advancedFilterTypes: advancedFilterTypes.map(filter => ({ ...filter, @@ -291,6 +294,21 @@ export default { selectedInboxes: [], isContextMenuOpen: false, appliedFilter: [], + infiniteLoaderOptions: { + root: this.$refs.conversationList, + rootMargin: '100px 0px 100px 0px', + }, + + itemComponent: ConversationItem, + // virtualListExtraProps is to pass the props to the conversationItem component. + virtualListExtraProps: { + label: this.label, + teamId: this.teamId, + foldersId: this.foldersId, + conversationType: this.conversationType, + showAssignee: false, + isConversationSelected: this.isConversationSelected, + }, }; }, computed: { @@ -509,16 +527,22 @@ export default { }, label() { this.resetAndFetchData(); + this.updateVirtualListProps('label', this.label); }, conversationType() { this.resetAndFetchData(); + this.updateVirtualListProps('conversationType', this.conversationType); }, activeFolder() { this.resetAndFetchData(); + this.updateVirtualListProps('foldersId', this.foldersId); }, chatLists() { this.chatsOnView = this.conversationList; }, + showAssigneeInConversationCard(newVal) { + this.updateVirtualListProps('showAssignee', newVal); + }, }, mounted() { this.setFiltersFromUISettings(); @@ -535,6 +559,12 @@ export default { }); }, methods: { + updateVirtualListProps(key, value) { + this.virtualListExtraProps = { + ...this.virtualListExtraProps, + [key]: value, + }; + }, onApplyFilter(payload) { this.resetBulkActions(); this.foldersQuery = filterQueryGenerator(payload); @@ -555,7 +585,10 @@ export default { const { conversations_filter_by: filterBy = {} } = this.uiSettings; const { status, order_by: orderBy } = filterBy; this.activeStatus = status || wootConstants.STATUS_TYPE.OPEN; - this.activeSortBy = orderBy || wootConstants.SORT_BY_TYPE.LATEST; + this.activeSortBy = + Object.keys(wootConstants.SORT_BY_TYPE).find( + sortField => sortField === orderBy + ) || wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC; }, onClickOpenAddFoldersModal() { this.showAddFoldersModal = true; @@ -635,10 +668,10 @@ export default { ); }, getKeyboardListenerParams() { - const allConversations = this.$refs.activeConversation.querySelectorAll( + const allConversations = this.$refs.conversationList.querySelectorAll( 'div.conversations-list div.conversation' ); - const activeConversation = this.$refs.activeConversation.querySelector( + const activeConversation = this.$refs.conversationList.querySelector( 'div.conversations-list div.conversation.active' ); const activeConversationIndex = [...allConversations].indexOf( @@ -694,9 +727,12 @@ export default { fetchConversations() { this.$store .dispatch('fetchAllConversations', this.conversationFilters) - .then(() => this.$emit('conversation-load')); + .then(this.emitConversationLoaded); }, loadMoreConversations() { + if (this.hasCurrentPageEndReached || this.chatListLoading) { + return; + } if (!this.hasAppliedFiltersOrActiveFolders) { this.fetchConversations(); } @@ -715,7 +751,7 @@ export default { queryData: filterQueryGenerator(payload), page, }) - .then(() => this.$emit('conversation-load')); + .then(this.emitConversationLoaded); this.showAdvancedFilters = false; }, fetchSavedFilteredConversations(payload) { @@ -725,7 +761,7 @@ export default { queryData: payload, page, }) - .then(() => this.$emit('conversation-load')); + .then(this.emitConversationLoaded); }, updateAssigneeTab(selectedTab) { if (this.activeAssigneeTab !== selectedTab) { @@ -737,6 +773,20 @@ export default { } } }, + emitConversationLoaded() { + this.$emit('conversation-load'); + this.$nextTick(() => { + // Addressing a known issue in the virtual list library where dynamically added items + // might not render correctly. This workaround involves a slight manual adjustment + // to the scroll position, triggering the list to refresh its rendering. + const virtualList = this.$refs.conversationVirtualList; + const scrollToOffset = virtualList?.scrollToOffset; + const currentOffset = virtualList?.getOffset() || 0; + if (scrollToOffset) { + scrollToOffset(currentOffset + 1); + } + }); + }, resetBulkActions() { this.selectedConversations = []; this.selectedInboxes = []; diff --git a/app/javascript/dashboard/components/ConversationItem.vue b/app/javascript/dashboard/components/ConversationItem.vue new file mode 100644 index 000000000..b013b0171 --- /dev/null +++ b/app/javascript/dashboard/components/ConversationItem.vue @@ -0,0 +1,72 @@ + + + diff --git a/app/javascript/dashboard/components/IntersectionObserver.vue b/app/javascript/dashboard/components/IntersectionObserver.vue new file mode 100644 index 000000000..215c054ff --- /dev/null +++ b/app/javascript/dashboard/components/IntersectionObserver.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index eb9b26f30..b5022c9a2 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -144,6 +144,7 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { ALLOWED_FILE_TYPES, ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP, + ALLOWED_FILE_TYPES_FOR_LINE, } from 'shared/constants/messages'; import VideoCallButton from '../VideoCallButton.vue'; import AIAssistanceButton from '../AIAssistanceButton.vue'; @@ -270,6 +271,9 @@ export default { return this.showFileUpload || this.isNote; }, showAudioRecorderButton() { + if (this.isALineChannel) { + return false; + } // Disable audio recorder for safari browser as recording is not supported const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test( navigator.userAgent @@ -291,6 +295,9 @@ export default { if (this.isATwilioWhatsAppChannel) { return ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP; } + if (this.isALineChannel) { + return ALLOWED_FILE_TYPES_FOR_LINE; + } return ALLOWED_FILE_TYPES; }, enableDragAndDrop() { diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue index 01a6bb442..f424fb693 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue @@ -34,7 +34,7 @@ type="sort" :selected-value="sortFilter" :items="chatSortItems" - path-prefix="CHAT_LIST.CHAT_SORT_FILTER_ITEMS" + path-prefix="CHAT_LIST.SORT_ORDER_ITEMS" @onChangeFilter="onChangeFilter" /> @@ -58,7 +58,7 @@ export default { return { showActionsDropdown: false, chatStatusItems: this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS'), - chatSortItems: this.$t('CHAT_LIST.CHAT_SORT_FILTER_ITEMS'), + chatSortItems: this.$t('CHAT_LIST.SORT_ORDER_ITEMS'), }; }, computed: { @@ -70,7 +70,9 @@ export default { return this.chatStatusFilter || wootConstants.STATUS_TYPE.OPEN; }, sortFilter() { - return this.chatSortFilter || wootConstants.SORT_BY_TYPE.LATEST; + return ( + this.chatSortFilter || wootConstants.SORT_BY_TYPE.LAST_ACTIVITY_AT_DESC + ); }, }, methods: { diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue index 635600787..a19ac630a 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue @@ -31,7 +31,7 @@ size="40px" />
@@ -175,6 +175,10 @@ export default { type: Boolean, default: false, }, + enableContextMenu: { + type: Boolean, + default: false, + }, }, data() { return { @@ -289,6 +293,7 @@ export default { this.$emit(action, this.chat.id, this.inbox.id); }, openContextMenu(e) { + if (!this.enableContextMenu) return; e.preventDefault(); this.$emit('context-menu-toggle', true); this.contextMenu.x = e.pageX || e.clientX; diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index d7c9742ae..34ad5f59f 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -1,7 +1,7 @@ - + + + {{ $t('CHAT_LIST.ATTACHMENTS.image.CONTENT') }} + + {{ parsedLastMessage }} @@ -88,6 +96,9 @@ export default { attachmentMessageContent() { return `CHAT_LIST.ATTACHMENTS.${this.lastMessageFileType}.CONTENT`; }, + isMessageSticker() { + return this.message && this.message.content_type === 'sticker'; + }, }, }; diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 10b1dce32..d9e255c6d 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -284,10 +284,12 @@ export default { return this.currentChat.unread_count || 0; }, inboxSupportsReplyTo() { - return { - incoming: this.inboxHasFeature(INBOX_FEATURES.REPLY_TO), - outgoing: this.inboxHasFeature(INBOX_FEATURES.REPLY_TO_OUTGOING), - }; + const incoming = this.inboxHasFeature(INBOX_FEATURES.REPLY_TO); + const outgoing = + this.inboxHasFeature(INBOX_FEATURES.REPLY_TO_OUTGOING) && + !this.is360DialogWhatsAppChannel; + + return { incoming, outgoing }; }, }, diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 5e1f34904..4199be673 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -275,7 +275,8 @@ export default { return ( this.inReplyTo?.id && !this.isPrivate && - this.inboxHasFeature(INBOX_FEATURES.REPLY_TO) + this.inboxHasFeature(INBOX_FEATURES.REPLY_TO) && + !this.is360DialogWhatsAppChannel ); }, showRichContentEditor() { @@ -392,7 +393,8 @@ export default { this.isAPIInbox || this.isAnEmailChannel || this.isASmsInbox || - this.isATelegramChannel + this.isATelegramChannel || + this.isALineChannel ); }, replyButtonLabel() { @@ -495,7 +497,11 @@ export default { return `draft-${this.conversationIdByRoute}-${this.replyType}`; }, audioRecordFormat() { - if (this.isAWhatsAppChannel || this.isAPIInbox) { + if ( + this.isAWhatsAppChannel || + this.isAPIInbox || + this.isATelegramChannel + ) { return AUDIO_FORMATS.OGG; } return AUDIO_FORMATS.WAV; @@ -624,6 +630,8 @@ export default { `${this.$t('CONVERSATION.REPLYBOX.INSERT_READ_MORE')} ${url}` ); } + + this.$track(CONVERSATION_EVENTS.INSERT_ARTICLE_LINK); }, toggleRichContentEditor() { this.updateUISettings({ diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/Text.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/Text.vue index e6aa951dd..a9127ac16 100644 --- a/app/javascript/dashboard/components/widgets/conversation/bubble/Text.vue +++ b/app/javascript/dashboard/components/widgets/conversation/bubble/Text.vue @@ -7,7 +7,11 @@ }" >
- +
+ +
{{ $t('HELP_CENTER.TABLE.HEADERS.CATEGORY') }}
{{ $t('HELP_CENTER.TABLE.HEADERS.STATUS') }}
diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue index 6069b44b4..f48b79971 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue @@ -91,6 +91,7 @@ @search-change="handleSearchChange" @close="onBlur" @tag="addTagValue" + @remove="removeTag" />
@@ -157,16 +158,23 @@ export default { return this.metaTags.map(item => item.name); }, }, + watch: { + article: { + handler() { + if (!isEmptyObject(this.article.meta || {})) { + const { + meta: { title = '', description = '', tags = [] }, + } = this.article; + this.metaTitle = title; + this.metaDescription = description; + this.metaTags = this.formattedTags({ tags }); + } + }, + deep: true, + immediate: true, + }, + }, mounted() { - if (!isEmptyObject(this.article.meta || {})) { - const { - meta: { title = '', description = '', tags = [] }, - } = this.article; - this.metaTitle = title; - this.metaDescription = description; - this.metaTags = this.formattedTags({ tags }); - } - this.saveArticle = debounce( () => { this.$emit('save-article', { @@ -196,6 +204,9 @@ export default { this.metaTags.push(...this.formattedTags({ tags: [...new Set(tags)] })); this.saveArticle(); }, + removeTag() { + this.saveArticle(); + }, handleSearchChange(value) { this.tagInputValue = value; }, diff --git a/app/javascript/dashboard/routes/dashboard/notifications/routes.js b/app/javascript/dashboard/routes/dashboard/notifications/routes.js index de3411f57..38812fc92 100644 --- a/app/javascript/dashboard/routes/dashboard/notifications/routes.js +++ b/app/javascript/dashboard/routes/dashboard/notifications/routes.js @@ -1,7 +1,7 @@ /* eslint arrow-body-style: 0 */ -import NotificationsView from './components/NotificationsView.vue'; import { frontendURL } from '../../../helper/URLHelper'; -import SettingsWrapper from '../settings/Wrapper'; +const SettingsWrapper = () => import('../settings/Wrapper.vue'); +const NotificationsView = () => import('./components/NotificationsView.vue'); export const routes = [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js index 5e3df43b4..0acfd5bd5 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index.vue'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js b/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js index e06f18a0b..382594c73 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js @@ -1,8 +1,8 @@ -import SettingsContent from '../Wrapper'; const Bot = () => import('./Index.vue'); const CsmlEditBot = () => import('./csml/Edit.vue'); const CsmlNewBot = () => import('./csml/New.vue'); import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js b/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js index 3bf4b6d2e..cd23432ff 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import AgentHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const AgentHome = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js b/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js index a4430ff1e..72d79a9de 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import AttributesHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const AttributesHome = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js index f1fb18ec1..00c1fda74 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import AuditLogsHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const AuditLogsHome = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js b/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js index dbb204261..3a5d6c887 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import Automation from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Automation = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js b/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js index db28fb844..7a37f320a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js @@ -1,6 +1,6 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index.vue'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js index 4de15b068..149593b7e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js @@ -1,6 +1,6 @@ -import Index from './Index'; -import SettingsContent from '../Wrapper'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ @@ -20,7 +20,7 @@ export default { path: 'ongoing', name: 'settings_account_campaigns', roles: ['administrator'], - component: { ...Index }, + component: Index, }, ], }, @@ -36,7 +36,7 @@ export default { path: 'one_off', name: 'one_off', roles: ['administrator'], - component: { ...Index }, + component: Index, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js index 3b68de1db..73b370e2a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import CannedHome from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const CannedHome = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js b/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js index 9423a915d..4e4c14c00 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js @@ -1,13 +1,14 @@ /* eslint arrow-body-style: 0 */ -import SettingsContent from '../Wrapper'; -import Settings from './Settings'; -import InboxHome from './Index'; -import InboxChannel from './InboxChannels'; -import ChannelList from './ChannelList'; -import channelFactory from './channel-factory'; -import AddAgents from './AddAgents'; -import FinishSetup from './FinishSetup'; import { frontendURL } from '../../../../helper/URLHelper'; +import channelFactory from './channel-factory'; + +const SettingsContent = () => import('../Wrapper.vue'); +const InboxHome = () => import('./Index.vue'); +const Settings = () => import('./Settings.vue'); +const InboxChannel = () => import('./InboxChannels.vue'); +const ChannelList = () => import('./ChannelList.vue'); +const AddAgents = () => import('./AddAgents.vue'); +const FinishSetup = () => import('./FinishSetup.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js index 67f5a5f07..a0f8477b2 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js @@ -1,7 +1,7 @@ -import Index from './Index'; -import SettingsContent from '../Wrapper'; -import IntegrationHooks from './IntegrationHooks'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const IntegrationHooks = () => import('./IntegrationHooks.vue'); +const Index = () => import('./Index.vue'); export default { routes: [ diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js index e2be34338..542cf591e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js @@ -1,11 +1,12 @@ -import Index from './Index'; -import SettingsContent from '../Wrapper'; -import Webhook from './Webhooks/Index'; -import DashboardApps from './DashboardApps/Index'; -import ShowIntegration from './ShowIntegration'; -import Slack from './Slack'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Webhook = () => import('./Webhooks/Index.vue'); +const DashboardApps = () => import('./DashboardApps/Index.vue'); +const ShowIntegration = () => import('./ShowIntegration.vue'); +const Slack = () => import('./Slack.vue'); +const Index = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js index 6424aaea4..4fc514da6 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js @@ -1,7 +1,8 @@ -import SettingsContent from '../Wrapper'; -import Index from './Index'; import { frontendURL } from '../../../../helper/URLHelper'; +const SettingsContent = () => import('../Wrapper.vue'); +const Index = () => import('./Index.vue'); + export default { routes: [ { diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue index cc161bf4f..3cebed550 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue @@ -1,6 +1,8 @@ + <% end %> + + -
-
+
+
<% if !@is_plain_layout_enabled %> <%= render "public/api/v1/portals/header", portal: @portal %> <% end %> @@ -42,15 +70,53 @@ By default, it renders:
+ diff --git a/app/views/super_admin/application/_nav_item.html.erb b/app/views/super_admin/application/_nav_item.html.erb new file mode 100644 index 000000000..a6366d8b5 --- /dev/null +++ b/app/views/super_admin/application/_nav_item.html.erb @@ -0,0 +1,9 @@ +
  • + <% text_class_name = current_page?(url) ? 'text-woot-500 bg-slate-25' : 'text-slate-800' %> + <%= link_to(url, class: text_class_name + " -ml-1 focus:outline-none cursor-pointer flex items-center px-2 py-1.5 text-slate-800 cursor-pointer hover:text-woot-500 hover:bg-slate-25 rounded-lg") do %> + + <%= label %> + <% end %> +
  • diff --git a/app/views/super_admin/application/_navigation.html.erb b/app/views/super_admin/application/_navigation.html.erb index b85c02623..bbbc47b2e 100644 --- a/app/views/super_admin/application/_navigation.html.erb +++ b/app/views/super_admin/application/_navigation.html.erb @@ -6,88 +6,56 @@ By default, the navigation contains navigation links for all resources in the admin dashboard, as defined by the routes in the `admin/` namespace %> - <%= javascript_pack_tag 'superadmin_pages' %> -<%= stylesheet_pack_tag 'superadmin_pages' %> + <% sidebar_icons = { - accounts: 'ion ion-briefcase', - users: 'ion ion-person-stalker', - super_admins: 'ion ion-unlocked', - access_tokens: 'ion-key', - platform_apps: 'ion ion-social-buffer', - installation_configs: 'ion ion-settings', - agent_bots: 'ion ion-social-android', + accounts: 'icon-building-4-line', + users: 'icon-user-follow-line', + platform_apps: 'icon-apps-2-line', + agent_bots: 'icon-robot-line', } %> -
    diff --git a/app/views/super_admin/instance_statuses/show.html.erb b/app/views/super_admin/instance_statuses/show.html.erb index d0d23f2a1..5474bff6a 100644 --- a/app/views/super_admin/instance_statuses/show.html.erb +++ b/app/views/super_admin/instance_statuses/show.html.erb @@ -1,5 +1,5 @@ <% content_for(:title) do %> - Instance Health + Instance Status <% end %>