diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e3153a7a..f8ffdf076 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ defaults: &defaults working_directory: ~/build docker: # specify the version you desire here - - image: circleci/ruby:2.7.3-node-browsers + - image: circleci/ruby:3.0.2-node-browsers # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/.devcontainer/Dockerfile.base b/.devcontainer/Dockerfile.base index dcbbd27ca..de007e202 100644 --- a/.devcontainer/Dockerfile.base +++ b/.devcontainer/Dockerfile.base @@ -1,5 +1,5 @@ # pre-build stage -ARG VARIANT=2.7 +ARG VARIANT=3 FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT} # Update args in docker-compose.yaml to set the UID/GID of the "vscode" user. diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 6a2ffbea6..36e4e8214 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -10,8 +10,8 @@ services: context: .. dockerfile: .devcontainer/Dockerfile args: - # Update 'VARIANT' to pick a Ruby version: 2, 2.7, 2.6, 2.5 - VARIANT: 2.7 + # Update 'VARIANT' to pick a Ruby version: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ruby + VARIANT: 3 # [Choice] Install Node.js INSTALL_NODE: "true" NODE_VERSION: "lts/*" diff --git a/.env.example b/.env.example index 6dffe6b38..eb41a88f5 100644 --- a/.env.example +++ b/.env.example @@ -147,6 +147,10 @@ USE_INBOX_AVATAR_FOR_BOT=true # maxmindb api key to use geoip2 service # IP_LOOKUP_API_KEY= +## Rack Attack configuration +## To prevent and throttle abusive requests +# ENABLE_RACK_ATTACK=false + ## Running chatwoot as an API only server ## setting this value to true will disable the frontend dashboard endpoints @@ -157,3 +161,7 @@ USE_INBOX_AVATAR_FOR_BOT=true # LETTER_OPENER=true # meant to be used in github codespaces # WEBPACKER_DEV_SERVER_PUBLIC= + +# If you want to use official mobile app, +# the notifications would be relayed via a Chatwoot server +ENABLE_PUSH_RELAY_SERVER=true diff --git a/.nvmrc b/.nvmrc index 66df3b7ab..0627d197d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12.16.1 +14.17.4 diff --git a/.rubocop.yml b/.rubocop.yml index 4bec94c82..c05cbb9bf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -13,6 +13,7 @@ Metrics/ClassLength: - 'app/models/conversation.rb' - 'app/mailers/conversation_reply_mailer.rb' - 'app/models/message.rb' + - 'app/builders/messages/facebook/message_builder.rb' RSpec/ExampleLength: Max: 25 Style/Documentation: @@ -24,12 +25,13 @@ Style/FrozenStringLiteralComment: Style/SymbolArray: Enabled: false Style/OptionalBooleanParameter: - Exclude: + Exclude: - 'app/services/email_templates/db_resolver_service.rb' - 'app/dispatchers/dispatcher.rb' Style/GlobalVars: Exclude: - - 'config/initializers/redis.rb' + - 'config/initializers/01_redis.rb' + - 'config/initializers/rack_attack.rb' - 'lib/redis/alfred.rb' - 'lib/global_config.rb' Style/ClassVars: @@ -38,6 +40,14 @@ Style/ClassVars: Lint/MissingSuper: Exclude: - 'app/drops/base_drop.rb' +Lint/SymbolConversion: + Enabled: false +Lint/EmptyBlock: + Exclude: + - 'app/views/api/v1/accounts/conversations/toggle_status.json.jbuilder' +Lint/OrAssignmentToConstant: + Exclude: + - 'lib/redis/config.rb' Metrics/BlockLength: Exclude: - spec/**/* @@ -55,6 +65,11 @@ Rails/ApplicationController: - 'app/controllers/widgets_controller.rb' - 'app/controllers/platform_controller.rb' - 'app/controllers/public_controller.rb' + - 'app/controllers/survey/responses_controller.rb' +Rails/EnvironmentVariableAccess: + Enabled: false +Rails/TimeZoneAssignment: + Enabled: false Style/ClassAndModuleChildren: EnforcedStyle: compact Exclude: @@ -64,6 +79,10 @@ RSpec/NestedGroups: Max: 4 RSpec/MessageSpies: Enabled: false +RSpec/StubbedMock: + Enabled: false +Naming/VariableNumber: + Enabled: false Metrics/MethodLength: Exclude: - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' @@ -77,7 +96,7 @@ Style/GuardClause: - 'app/models/message.rb' - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' Metrics/AbcSize: - Exclude: + Exclude: - 'app/controllers/concerns/auth_helper.rb' - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' @@ -106,19 +125,24 @@ Rails/BulkChangeTable: - 'db/migrate/20191027054756_create_contact_inboxes.rb' - 'db/migrate/20191130164019_add_template_type_to_messages.rb' - 'db/migrate/20210425093724_convert_integration_hook_settings_field.rb' -Rails/UniqueValidationWithoutIndex: +Rails/UniqueValidationWithoutIndex: Exclude: - 'app/models/channel/twitter_profile.rb' - 'app/models/webhook.rb' - 'app/models/contact.rb' + - 'app/models/integrations/hook.rb' Rails/RenderInline: Exclude: - 'app/controllers/swagger_controller.rb' +Performance/CollectionLiteralInLoop: + Exclude: + - 'db/migrate/20210315101919_enable_email_channel.rb' RSpec/NamedSubject: Enabled: false # we should bring this down RSpec/MultipleMemoizedHelpers: Max: 12 + AllCops: NewCops: enable Exclude: @@ -133,4 +157,11 @@ AllCops: - 'tmp/**/*' - 'storage/**/*' - 'db/migrate/20200225162150_init_schema.rb' - - 'config/initializers/azure_storage_service_patch.rb' + - 'db/migrate/20210611180222_create_active_storage_variant_records.active_storage.rb' + - 'db/migrate/20210611180221_add_service_name_to_active_storage_blobs.active_storage.rb' + - db/migrate/20200309213132_add_account_id_to_agent_bot_inboxes.rb + - db/migrate/20200331095710_add_identifier_to_contact.rb + - db/migrate/20200429082655_add_medium_to_twilio_sms.rb + - db/migrate/20200503151130_add_account_feature_flag.rb + - db/migrate/20200927135222_add_last_activity_at_to_conversation.rb + - db/migrate/20210306170117_add_last_activity_at_to_contacts.rb diff --git a/.ruby-version b/.ruby-version index 2c9b4ef42..b50214693 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.3 +3.0.2 diff --git a/Gemfile b/Gemfile index 0f285c6fd..c172b7d08 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby '2.7.3' +ruby '3.0.2' ##-- base gems for rails --## gem 'rack-cors', require: 'rack/cors' @@ -33,19 +33,22 @@ gem 'liquid' gem 'commonmarker' # Validate Data against JSON Schema gem 'json_schemer' +# Rack middleware for blocking & throttling abusive requests +gem 'rack-attack' +# a utility tool for streaming, flexible and safe downloading of remote files +gem 'down', '~> 5.0' ##-- for active storage --## gem 'aws-sdk-s3', require: false gem 'azure-storage-blob', require: false gem 'google-cloud-storage', require: false -gem 'mini_magick' +gem 'image_processing' ##-- gems for database --# gem 'groupdate' gem 'pg' gem 'redis' gem 'redis-namespace' -gem 'redis-rack-cache' # super fast record imports in bulk gem 'activerecord-import' @@ -90,8 +93,11 @@ gem 'google-cloud-dialogflow' ##--- gems for debugging and error reporting ---## # static analysis gem 'brakeman' +gem 'ddtrace' gem 'scout_apm' -gem 'sentry-raven' +gem 'sentry-rails' +gem 'sentry-ruby' +gem 'sentry-sidekiq' ##-- background job processing --## gem 'sidekiq' @@ -120,7 +126,7 @@ group :development do gem 'web-console' # used in swagger build - gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: '131b11294fd6af9c428171f38516e6222a58c874' + gem 'json_refs' # When we want to squash migrations gem 'squasher' @@ -134,19 +140,19 @@ group :test do end group :development, :test do + gem 'active_record_query_trace' gem 'bundle-audit', require: false gem 'byebug', platform: :mri gem 'factory_bot_rails' gem 'faker' gem 'listen' - gem 'mock_redis', git: 'https://github.com/sds/mock_redis', ref: '16d00789f0341a3aac35126c0ffe97a596753ff9' + gem 'mock_redis' gem 'pry-rails' - gem 'rspec-rails', '~> 4.0.0.beta2' + gem 'rspec-rails', '~> 5.0.0' gem 'rubocop', require: false gem 'rubocop-performance', require: false gem 'rubocop-rails', require: false gem 'rubocop-rspec', require: false - gem 'scss_lint', require: false gem 'seed_dump' gem 'shoulda-matchers' gem 'simplecov', '0.17.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 86d421fa7..30c6c022a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,80 +1,70 @@ -GIT - remote: https://github.com/sds/mock_redis - revision: 16d00789f0341a3aac35126c0ffe97a596753ff9 - ref: 16d00789f0341a3aac35126c0ffe97a596753ff9 - specs: - mock_redis (0.22.0) - -GIT - remote: https://github.com/tzmfreedom/json_refs - revision: 131b11294fd6af9c428171f38516e6222a58c874 - ref: 131b11294fd6af9c428171f38516e6222a58c874 - specs: - json_refs (0.1.6) - hana - GEM remote: https://rubygems.org/ specs: - actioncable (6.0.3.7) - actionpack (= 6.0.3.7) + actioncable (6.1.4) + actionpack (= 6.1.4) + activesupport (= 6.1.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.7) - actionpack (= 6.0.3.7) - activejob (= 6.0.3.7) - activerecord (= 6.0.3.7) - activestorage (= 6.0.3.7) - activesupport (= 6.0.3.7) + actionmailbox (6.1.4) + actionpack (= 6.1.4) + activejob (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) mail (>= 2.7.1) - actionmailer (6.0.3.7) - actionpack (= 6.0.3.7) - actionview (= 6.0.3.7) - activejob (= 6.0.3.7) + actionmailer (6.1.4) + actionpack (= 6.1.4) + actionview (= 6.1.4) + activejob (= 6.1.4) + activesupport (= 6.1.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.7) - actionview (= 6.0.3.7) - activesupport (= 6.0.3.7) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.4) + actionview (= 6.1.4) + activesupport (= 6.1.4) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.7) - actionpack (= 6.0.3.7) - activerecord (= 6.0.3.7) - activestorage (= 6.0.3.7) - activesupport (= 6.0.3.7) + actiontext (6.1.4) + actionpack (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) nokogiri (>= 1.8.5) - actionview (6.0.3.7) - activesupport (= 6.0.3.7) + actionview (6.1.4) + activesupport (= 6.1.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.7) - activesupport (= 6.0.3.7) + active_record_query_trace (1.8) + activejob (6.1.4) + activesupport (= 6.1.4) globalid (>= 0.3.6) - activemodel (6.0.3.7) - activesupport (= 6.0.3.7) - activerecord (6.0.3.7) - activemodel (= 6.0.3.7) - activesupport (= 6.0.3.7) - activerecord-import (1.0.7) + activemodel (6.1.4) + activesupport (= 6.1.4) + activerecord (6.1.4) + activemodel (= 6.1.4) + activesupport (= 6.1.4) + activerecord-import (1.2.0) activerecord (>= 3.2) - activestorage (6.0.3.7) - actionpack (= 6.0.3.7) - activejob (= 6.0.3.7) - activerecord (= 6.0.3.7) + activestorage (6.1.4) + actionpack (= 6.1.4) + activejob (= 6.1.4) + activerecord (= 6.1.4) + activesupport (= 6.1.4) marcel (~> 1.0.0) - activesupport (6.0.3.7) + mini_mime (>= 1.1.0) + activesupport (6.1.4) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - acts-as-taggable-on (6.5.0) - activerecord (>= 5.0, < 6.1) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + acts-as-taggable-on (8.1.0) + activerecord (>= 5.0, < 6.2) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) administrate (0.16.0) @@ -90,23 +80,23 @@ GEM annotate (3.1.1) activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 14.0) - ast (2.4.1) + ast (2.4.2) attr_extras (6.2.4) - aws-eventstream (1.1.0) - aws-partitions (1.360.0) - aws-sdk-core (3.105.0) + aws-eventstream (1.1.1) + aws-partitions (1.482.0) + aws-sdk-core (3.119.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.37.0) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-kms (1.46.0) + aws-sdk-core (~> 3, >= 3.119.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.79.1) - aws-sdk-core (~> 3, >= 3.104.3) + aws-sdk-s3 (1.98.0) + aws-sdk-core (~> 3, >= 3.119.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.2) + aws-sigv4 (1.2.4) aws-eventstream (~> 1, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) @@ -120,42 +110,48 @@ GEM faraday_middleware (~> 1.0.0.rc1) net-http-persistent (~> 4.0) nokogiri (~> 1.11.0.rc2) - barnes (0.0.8) + barnes (0.0.9) multi_json (~> 1) statsd-ruby (~> 1.1) bcrypt (3.1.16) bindex (0.8.1) - bootsnap (1.4.8) + bootsnap (1.7.6) msgpack (~> 1.0) - brakeman (4.9.0) + brakeman (5.1.1) browser (5.3.1) builder (3.2.4) - bullet (6.1.0) + bullet (6.1.4) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundle-audit (0.1.0) bundler-audit - bundler-audit (0.7.0.1) + bundler-audit (0.8.0) bundler (>= 1.2.0, < 3) - thor (>= 0.18, < 2) + thor (~> 1.0) byebug (11.1.3) coderay (1.1.3) coercible (1.0.0) descendants_tracker (~> 0.0.1) - commonmarker (0.21.1) - ruby-enum (~> 0.5) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - crack (0.4.3) - safe_yaml (~> 1.0.0) + commonmarker (0.22.0) + concurrent-ruby (1.1.9) + connection_pool (2.2.5) + crack (0.4.5) + rexml crass (1.0.6) - cypress-on-rails (1.8.0) + cypress-on-rails (1.10.1) rack - database_cleaner (1.8.5) + database_cleaner (2.0.1) + database_cleaner-active_record (~> 2.0.0) + database_cleaner-active_record (2.0.1) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) datetime_picker_rails (0.0.7) momentjs-rails (>= 2.8.1) + ddtrace (0.51.1) + ffi (~> 1.0) + msgpack declarative (0.0.20) - declarative-option (0.1.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) devise (4.8.0) @@ -167,48 +163,49 @@ GEM devise-secure_password (2.0.1) devise (>= 4.0.0, < 5.0.0) railties (>= 5.0.0, < 7.0.0) - devise_token_auth (1.1.4) + devise_token_auth (1.2.0) bcrypt (~> 3.0) devise (> 3.5.2, < 5) - rails (>= 4.2.0, < 6.1) - sprockets (= 3.7.2) + rails (>= 4.2.0, < 6.2) diff-lcs (1.4.4) - digest-crc (0.6.1) - rake (~> 13.0) - docile (1.3.2) + digest-crc (0.6.4) + rake (>= 12.0.0, < 14.0.0) + docile (1.4.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - ecma-re-validator (0.2.1) - regexp_parser (~> 1.2) - equalizer (0.0.11) + down (5.2.3) + addressable (~> 2.8) + dry-inflector (0.2.1) + ecma-re-validator (0.3.0) + regexp_parser (~> 2.0) erubi (1.10.0) et-orbi (1.2.4) tzinfo - execjs (2.7.0) + execjs (2.8.1) facebook-messenger (2.0.1) httparty (~> 0.13, >= 0.13.7) rack (>= 1.4.5) - factory_bot (6.1.0) + factory_bot (6.2.0) activesupport (>= 5.0.0) - factory_bot_rails (6.1.0) - factory_bot (~> 6.1.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.13.0) + faker (2.18.0) i18n (>= 1.6, < 2) faraday (1.0.1) multipart-post (>= 1.2, < 3) faraday_middleware (1.0.0) faraday (~> 1.0) - fcm (1.0.2) - faraday (~> 1.0.0) - ffi (1.15.0) + fcm (1.0.3) + faraday (~> 1) + ffi (1.15.3) flag_shih_tzu (0.3.23) foreman (0.87.2) - fugit (1.4.1) + fugit (1.5.0) et-orbi (~> 1.1, >= 1.1.8) raabro (~> 1.4) gapic-common (0.3.4) @@ -217,18 +214,23 @@ GEM googleapis-common-protos-types (>= 1.0.4, < 2.0) googleauth (~> 0.9) grpc (~> 1.25) - geocoder (1.6.3) - gli (2.19.2) - globalid (0.4.2) - activesupport (>= 4.2.0) - google-api-client (0.43.0) + geocoder (1.6.7) + gli (2.20.1) + globalid (0.5.1) + activesupport (>= 5.0) + google-apis-core (0.4.1) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.6.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-storage_v1 (0.6.0) + google-apis-core (>= 0.4, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) @@ -241,21 +243,23 @@ GEM google-cloud-env (1.5.0) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.1.0) - google-cloud-storage (1.28.0) + google-cloud-storage (1.34.1) addressable (~> 2.5) digest-crc (~> 0.4) - google-api-client (~> 0.33) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - google-protobuf (3.15.8) + google-protobuf (3.17.3-universal-darwin) + google-protobuf (3.17.3-x86_64-linux) googleapis-common-protos (1.3.11) google-protobuf (~> 3.14) googleapis-common-protos-types (>= 1.0.6, < 2.0) grpc (~> 1.27) - googleapis-common-protos-types (1.0.6) + googleapis-common-protos-types (1.1.0) google-protobuf (~> 3.14) - googleauth (0.16.2) + googleauth (0.17.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -264,11 +268,14 @@ GEM signet (~> 0.14) groupdate (5.2.2) activesupport (>= 5) - grpc (1.37.1) + grpc (1.38.0-universal-darwin) google-protobuf (~> 3.15) googleapis-common-protos-types (~> 1.0) - haikunator (1.1.0) - hairtrigger (0.2.23) + grpc (1.38.0-x86_64-linux) + google-protobuf (~> 3.15) + googleapis-common-protos-types (~> 1.0) + haikunator (1.1.1) + hairtrigger (0.2.24) activerecord (>= 5.0, < 7) ruby2ruby (~> 2.4) ruby_parser (~> 3.10) @@ -277,7 +284,7 @@ GEM hashie (4.1.0) hkdf (0.3.0) http-accept (1.7.0) - http-cookie (1.0.3) + http-cookie (1.0.4) domain_name (~> 0.5) httparty (0.18.1) mime-types (~> 3.0) @@ -286,19 +293,23 @@ GEM i18n (1.8.10) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - inflecto (0.0.2) - jbuilder (2.10.0) + image_processing (1.12.1) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) + jbuilder (2.11.2) activesupport (>= 5.0.0) jmespath (1.4.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.3.1) - json_schemer (0.2.16) - ecma-re-validator (~> 0.2) + json (2.5.1) + json_refs (0.1.6) + hana + json_schemer (0.2.18) + ecma-re-validator (~> 0.3) hana (~> 1.3) - regexp_parser (~> 1.5) + regexp_parser (~> 2.0) uri_template (~> 0.7) jwt (2.2.3) kaminari (1.2.1) @@ -321,11 +332,11 @@ GEM addressable (~> 2.7) letter_opener (1.7.0) launchy (~> 2.2) - liquid (4.0.3) - listen (3.3.3) + liquid (5.0.1) + listen (3.6.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.9.1) + loofah (2.11.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -336,98 +347,97 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0225) - mini_magick (4.10.1) + mime-types-data (3.2021.0704) + mini_magick (4.11.0) mini_mime (1.1.0) - mini_portile2 (2.5.1) minitest (5.14.4) + mock_redis (0.28.0) + ruby2_keywords momentjs-rails (2.20.1) railties (>= 3.1) - msgpack (1.3.3) + msgpack (1.4.2) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) - net-http-persistent (4.0.0) + net-http-persistent (4.0.1) connection_pool (~> 2.2) netrc (0.11.0) nio4r (2.5.7) - nokogiri (1.11.6) - mini_portile2 (~> 2.5.0) + nokogiri (1.11.7-arm64-darwin) + racc (~> 1.4) + nokogiri (1.11.7-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.11.7-x86_64-linux) racc (~> 1.4) oauth (0.5.6) orm_adapter (0.5.0) os (1.1.1) - parallel (1.19.2) - parser (2.7.1.4) + parallel (1.20.1) + parser (3.0.2.0) ast (~> 2.4.1) pg (1.2.3) - procore-sift (0.15.0) + procore-sift (0.16.0) rails (> 4.2.0) - pry (0.13.1) + pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (4.3.8) + puma (5.4.0) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.5.2) rack (2.2.3) - rack-cache (1.12.0) - rack (>= 0.4) + rack-attack (6.5.0) + rack (>= 1.0, < 3) rack-cors (1.1.1) rack (>= 2.0.0) - rack-proxy (0.6.5) + rack-proxy (0.7.0) rack rack-test (1.1.0) rack (>= 1.0, < 3) rack-timeout (0.6.0) - rails (6.0.3.7) - actioncable (= 6.0.3.7) - actionmailbox (= 6.0.3.7) - actionmailer (= 6.0.3.7) - actionpack (= 6.0.3.7) - actiontext (= 6.0.3.7) - actionview (= 6.0.3.7) - activejob (= 6.0.3.7) - activemodel (= 6.0.3.7) - activerecord (= 6.0.3.7) - activestorage (= 6.0.3.7) - activesupport (= 6.0.3.7) - bundler (>= 1.3.0) - railties (= 6.0.3.7) + rails (6.1.4) + actioncable (= 6.1.4) + actionmailbox (= 6.1.4) + actionmailer (= 6.1.4) + actionpack (= 6.1.4) + actiontext (= 6.1.4) + actionview (= 6.1.4) + activejob (= 6.1.4) + activemodel (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) + bundler (>= 1.15.0) + railties (= 6.1.4) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.3.7) - actionpack (= 6.0.3.7) - activesupport (= 6.0.3.7) + railties (6.1.4) + actionpack (= 6.1.4) + activesupport (= 6.1.4) method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) + rake (>= 0.13) + thor (~> 1.0) rainbow (3.0.0) - rake (13.0.3) - rb-fsevent (0.10.4) + rake (13.0.6) + rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) - redis (4.2.1) - redis-namespace (1.8.0) + redis (4.4.0) + redis-namespace (1.8.1) redis (>= 3.0.4) - redis-rack-cache (2.2.1) - rack-cache (>= 1.10, < 2) - redis-store (>= 1.6, < 2) - redis-store (1.9.0) - redis (>= 4, < 5) - regexp_parser (1.7.1) - representable (3.0.4) + regexp_parser (2.1.1) + representable (3.1.1) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) responders (3.0.1) actionpack (>= 5.0) @@ -439,57 +449,53 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.2.5) - rspec-core (3.9.2) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (4.0.1) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.9) - rspec-expectations (~> 3.9) - rspec-mocks (~> 3.9) - rspec-support (~> 3.9) - rspec-support (3.9.3) - rubocop (0.89.1) + rspec-support (~> 3.10.0) + rspec-rails (5.0.1) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.2) + rubocop (1.18.4) parallel (~> 1.10) - parser (>= 2.7.1.1) + parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.7) + regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 0.3.0, < 1.0) + rubocop-ast (>= 1.8.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.3.0) - parser (>= 2.7.1.4) - rubocop-performance (1.7.1) - rubocop (>= 0.82.0) - rubocop-rails (2.8.1) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.8.0) + parser (>= 3.0.1.1) + rubocop-performance (1.11.4) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.11.3) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.87.0) - rubocop-rspec (1.43.2) - rubocop (~> 0.87) - ruby-enum (0.9.0) - i18n - ruby-progressbar (1.10.1) + rubocop (>= 1.7.0, < 2.0) + rubocop-rspec (2.4.0) + rubocop (~> 1.0) + rubocop-ast (>= 1.1.0) + ruby-progressbar (1.11.0) + ruby-vips (2.1.2) + ffi (~> 1.12) + ruby2_keywords (0.0.5) ruby2ruby (2.4.4) ruby_parser (~> 3.1) sexp_processor (~> 4.6) - ruby_parser (3.15.0) - rubocop (>= 0.87.0) - sexp_processor (~> 4.9) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) + ruby_parser (3.16.0) + sexp_processor (~> 4.15, >= 4.15.1) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -498,21 +504,30 @@ GEM sprockets (> 3.0) sprockets-rails tilt - scout_apm (2.6.9) + scout_apm (4.1.1) parser - scss_lint (0.59.0) - sass (~> 3.5, >= 3.5.5) seed_dump (3.3.1) activerecord (>= 4) activesupport (>= 4) selectize-rails (0.12.6) - semantic_range (2.3.0) - sentry-raven (3.0.3) + semantic_range (3.0.0) + sentry-rails (4.6.4) + railties (>= 5.0) + sentry-ruby-core (~> 4.6.0) + sentry-ruby (4.6.4) + concurrent-ruby (~> 1.0, >= 1.0.2) faraday (>= 1.0) - sexp_processor (4.15.1) - shoulda-matchers (4.4.1) - activesupport (>= 4.2.0) - sidekiq (6.1.1) + sentry-ruby-core (= 4.6.4) + sentry-ruby-core (4.6.4) + concurrent-ruby + faraday + sentry-sidekiq (4.6.4) + sentry-ruby-core (~> 4.6.0) + sidekiq (>= 3.0) + sexp_processor (4.15.3) + shoulda-matchers (5.0.0) + activesupport (>= 5.2.0) + sidekiq (6.2.1) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -529,7 +544,7 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - slack-ruby-client (0.15.0) + slack-ruby-client (0.17.0) faraday (>= 1.0) faraday_middleware gli @@ -539,7 +554,7 @@ GEM spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (3.7.2) + sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.2) @@ -547,27 +562,28 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) squasher (0.6.2) - statsd-ruby (1.4.0) - telegram-bot-ruby (0.12.0) + statsd-ruby (1.5.0) + telegram-bot-ruby (0.16.0) + dry-inflector faraday - inflecto - virtus - telephone_number (1.4.9) + virtus (~> 2.0) + telephone_number (1.4.12) thor (1.1.0) thread_safe (0.3.6) tilt (2.0.10) time_diff (0.3.0) activesupport i18n + trailblazer-option (0.1.1) twilio-ruby (5.32.0) faraday (~> 1.0.0) jwt (>= 1.5, <= 2.5) nokogiri (>= 1.6, < 2.0) - twitty (0.1.1) + twitty (0.1.4) oauth - tzinfo (1.2.9) - thread_safe (~> 0.1) - tzinfo-data (1.2020.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + tzinfo-data (1.2021.1) tzinfo (>= 1.0.0) uber (0.1.0) uglifier (4.2.0) @@ -575,46 +591,49 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.7) - unicode-display_width (1.7.0) - uniform_notifier (1.13.0) + unicode-display_width (2.0.0) + uniform_notifier (1.14.2) uri_template (0.7.0) - valid_email2 (3.3.1) + valid_email2 (4.0.0) activemodel (>= 3.2) mail (~> 2.5) - virtus (1.0.5) + virtus (2.0.0) axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) - equalizer (~> 0.0, >= 0.0.9) warden (1.2.9) rack (>= 2.0.9) - web-console (4.0.4) + web-console (4.1.0) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.8.3) + webmock (3.13.0) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.2.1) + webpacker (5.4.0) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - webpush (1.0.0) + webpush (1.1.0) hkdf (~> 0.2) jwt (~> 2.0) - websocket-driver (0.7.3) + webrick (1.7.0) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wisper (2.0.0) zeitwerk (2.4.2) PLATFORMS - ruby + arm64-darwin-20 + x86_64-darwin-21 + x86_64-linux DEPENDENCIES + active_record_query_trace activerecord-import acts-as-taggable-on administrate @@ -632,10 +651,12 @@ DEPENDENCIES commonmarker cypress-on-rails (~> 1.0) database_cleaner + ddtrace devise devise-secure_password (~> 2.0) devise_token_auth dotenv-rails + down (~> 5.0) facebook-messenger factory_bot_rails faker @@ -649,8 +670,9 @@ DEPENDENCIES haikunator hairtrigger hashie + image_processing jbuilder - json_refs! + json_refs json_schemer jwt kaminari @@ -659,30 +681,30 @@ DEPENDENCIES liquid listen maxminddb - mini_magick - mock_redis! + mock_redis pg procore-sift pry-rails puma pundit + rack-attack rack-cors rack-timeout rails redis redis-namespace - redis-rack-cache responders rest-client - rspec-rails (~> 4.0.0.beta2) + rspec-rails (~> 5.0.0) rubocop rubocop-performance rubocop-rails rubocop-rspec scout_apm - scss_lint seed_dump - sentry-raven + sentry-rails + sentry-ruby + sentry-sidekiq shoulda-matchers sidekiq sidekiq-cron @@ -706,7 +728,7 @@ DEPENDENCIES wisper (= 2.0.0) RUBY VERSION - ruby 2.7.3p183 + ruby 3.0.2p107 BUNDLED WITH - 2.1.4 + 2.2.25 diff --git a/README.md b/README.md index 030b13dcd..2720de61e 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,8 @@ ___ Chatwoot is an open-source omnichannel customer support software. The development of Chatwoot started in 2016. It failed to succeed as a business and eventually shut up shop in 2017. During 2019 #Hacktoberfest, the maintainers decided to make it open-source, instead of letting the code rust in a private repo. With a pleasant surprise, Chatwoot became a trending project on Hacker News and best of all, got lots of love from the community. Now, a failed project is back on track and the prospects are looking great. The team is back to working on the project and this time, we are building it in the open. Thanks to the ideas and contributions from the community. ---- -### Features +## Features Chatwoot gives an integrated view of conversations happening in different communication channels. @@ -57,28 +56,22 @@ Other features include: - **Powerful API & Webhooks**: Extend the capability of the software using Chatwoot’s webhooks and APIs. - **Integrations**: Chatwoot natively integrates with Slack right now. Manage your conversations in Slack without logging into the dashboard. ---- +## Documentation -### Documentation +Detailed documentation is available at [chatwoot.com/help-center](https://www.chatwoot.com/help-center). -Detailed documentation is available at [www.chatwoot.com/help-center](https://www.chatwoot.com/help-center). - -### Translation process +## Translation process The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. Please read the [translation guide](https://www.chatwoot.com/docs/contributing/translating-chatwoot-to-your-language) for contributing to Chatwoot. ---- - -### Branching model +## Branching model We use the [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`. If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`. ---- +## Deployment -### Deployment - -#### Heroku one-click deploy +### Heroku one-click deploy Deploying Chatwoot to Heroku is a breeze. It's as simple as clicking this button: @@ -86,13 +79,21 @@ Deploying Chatwoot to Heroku is a breeze. It's as simple as clicking this button Follow this [link](https://www.chatwoot.com/docs/environment-variables) to understand setting the correct environment variables for the app to work with all the features. There might be breakages if you do not set the relevant environment variables. -#### Other deployment options +### Other deployment options Please follow [deployment architecture guide](https://www.chatwoot.com/docs/deployment/architecture) to deploy with Docker or Caprover. ---- +## Security -### Contributors ✨ +Looking to report a vulnerability? Please refer our [SECURITY.md](./SECURITY.md) file. + + +## Community? Questions? Support ? + +If you need help or just want to hang out, come, say hi on our [Discord](https://discord.gg/cJXdrwS) server. + + +## Contributors ✨ Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contributors): diff --git a/SECURITY.md b/SECURITY.md index 6a244bcdc..37e75995e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,31 @@ # Security Policy +Chatwoot is looking forward to working with security researchers across the world to keep Chatwoot and our users safe. If you have found an issue in our systems/applications, please reach out to us. ## Reporting a Vulnerability We use [huntr.dev](https://huntr.dev/) for security issues that affect our project. If you believe you have found a vulnerability, please disclose it via this [form](https://huntr.dev/bounties/disclose). + This will enable us to review the vulnerability, fix it promptly, and reward you for your efforts. -If you have any questions about the process, feel free to reach out to hello@chatwoot.com. +If you have any questions about the process, feel free to reach out to security@chatwoot.com. + + +## Out of scope + +Please do not perform testing against Chatwoot production services. Use a self hosted instance to perform tests. + +We consider the following to be out of scope, though there may be exceptions. + +- Missing HTTP security headers +- Self XSS +- HTTP Host Header XSS without working proof-of-concept +- Incomplete/Missing SPF/DKIM +- Denial of Service attacks +- DNSSEC +- Social Engineering attacks + +If you are not sure about the scope, please create a report. + +## Thanks + +Thank you for keeping Chatwoot and our users safe. 🙇 diff --git a/app.json b/app.json index cca08fec2..0d908761c 100644 --- a/app.json +++ b/app.json @@ -28,6 +28,10 @@ "FRONTEND_URL": { "description": "Public root URL of the Chatwoot installation. This will be used in the emails.", "value": "https://CHANGE.herokuapp.com" + }, + "INSTALLATION_ENV": { + "description": "Installation method used for Chatwoot.", + "value": "heroku" } }, "formation": { diff --git a/app/actions/contact_identify_action.rb b/app/actions/contact_identify_action.rb index 91060f215..655c6bc1c 100644 --- a/app/actions/contact_identify_action.rb +++ b/app/actions/contact_identify_action.rb @@ -3,8 +3,9 @@ class ContactIdentifyAction def perform ActiveRecord::Base.transaction do - @contact = merge_contact(existing_identified_contact, @contact) if merge_contacts?(existing_identified_contact, @contact) - @contact = merge_contact(existing_email_contact, @contact) if merge_contacts?(existing_email_contact, @contact) + merge_if_existing_identified_contact + merge_if_existing_email_contact + merge_if_existing_phone_number_contact update_contact end @contact @@ -16,6 +17,18 @@ class ContactIdentifyAction @account ||= @contact.account end + def merge_if_existing_identified_contact + @contact = merge_contact(existing_identified_contact, @contact) if merge_contacts?(existing_identified_contact, @contact) + end + + def merge_if_existing_email_contact + @contact = merge_contact(existing_email_contact, @contact) if merge_contacts?(existing_email_contact, @contact) + end + + def merge_if_existing_phone_number_contact + @contact = merge_contact(existing_phone_number_contact, @contact) if merge_contacts?(existing_phone_number_contact, @contact) + end + def existing_identified_contact return if params[:identifier].blank? @@ -28,6 +41,12 @@ class ContactIdentifyAction @existing_email_contact ||= Contact.where(account_id: account.id).find_by(email: params[:email]) end + def existing_phone_number_contact + return if params[:phone_number].blank? + + @existing_phone_number_contact ||= Contact.where(account_id: account.id).find_by(phone_number: params[:phone_number]) + end + def merge_contacts?(existing_contact, _contact) existing_contact && existing_contact.id != @contact.id end @@ -36,7 +55,9 @@ class ContactIdentifyAction custom_attributes = params[:custom_attributes] ? @contact.custom_attributes.merge(params[:custom_attributes]) : @contact.custom_attributes # blank identifier or email will throw unique index error # TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded - @contact.update!(params.slice(:name, :email, :identifier).reject { |_k, v| v.blank? }.merge({ custom_attributes: custom_attributes })) + @contact.update!(params.slice(:name, :email, :identifier, :phone_number).reject do |_k, v| + v.blank? + end.merge({ custom_attributes: custom_attributes })) ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present? end diff --git a/app/builders/contact_builder.rb b/app/builders/contact_builder.rb index 92c706a8a..ac24aca34 100644 --- a/app/builders/contact_builder.rb +++ b/app/builders/contact_builder.rb @@ -38,17 +38,30 @@ class ContactBuilder end def find_contact - contact = nil - - contact = account.contacts.find_by(identifier: contact_attributes[:identifier]) if contact_attributes[:identifier].present? - - contact ||= account.contacts.find_by(email: contact_attributes[:email]) if contact_attributes[:email].present? - - contact ||= account.contacts.find_by(phone_number: contact_attributes[:phone_number]) if contact_attributes[:phone_number].present? - + contact = find_contact_by_identifier(contact_attributes[:identifier]) + contact ||= find_contact_by_email(contact_attributes[:email]) + contact ||= find_contact_by_phone_number(contact_attributes[:phone_number]) contact end + def find_contact_by_identifier(identifier) + return if identifier.blank? + + account.contacts.find_by(identifier: identifier) + end + + def find_contact_by_email(email) + return if email.blank? + + account.contacts.find_by(email: email.downcase) + end + + def find_contact_by_phone_number(phone_number) + return if phone_number.blank? + + account.contacts.find_by(phone_number: phone_number) + end + def build_contact_inbox ActiveRecord::Base.transaction do contact = find_contact || create_contact @@ -57,6 +70,7 @@ class ContactBuilder contact_inbox rescue StandardError => e Rails.logger.info e + raise e end end end diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb index f0fc21f7d..23c9d92b4 100644 --- a/app/builders/messages/facebook/message_builder.rb +++ b/app/builders/messages/facebook/message_builder.rb @@ -17,12 +17,18 @@ class Messages::Facebook::MessageBuilder end def perform + # This channel might require reauthorization, may be owner might have changed the fb password + return if @inbox.channel.reauthorization_required? + ActiveRecord::Base.transaction do build_contact build_message end + ensure_contact_avatar + rescue Koala::Facebook::AuthenticationError + Rails.logger.info "Facebook Authorization expired for Inbox #{@inbox.id}" rescue StandardError => e - Raven.capture_exception(e) + Sentry.capture_exception(e) true end @@ -36,7 +42,6 @@ class Messages::Facebook::MessageBuilder return if contact.present? @contact = Contact.create!(contact_params.except(:remote_avatar_url)) - ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url] @contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id) end @@ -56,10 +61,21 @@ class Messages::Facebook::MessageBuilder end def attach_file(attachment, file_url) - file_resource = LocalResource.new(file_url) - attachment.file.attach(io: file_resource.file, filename: file_resource.filename, content_type: file_resource.encoding) - rescue *ExceptionList::URI_EXCEPTIONS => e - Rails.logger.info "invalid url #{file_url} : #{e.message}" + attachment_file = Down.download( + file_url + ) + attachment.file.attach( + io: attachment_file, + filename: attachment_file.original_filename, + content_type: attachment_file.content_type + ) + end + + def ensure_contact_avatar + return if contact_params[:remote_avatar_url].blank? + return if @contact.avatar.attached? + + ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) end def conversation @@ -136,9 +152,12 @@ class Messages::Facebook::MessageBuilder begin k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook? result = k.get_object(@sender_id) || {} + rescue Koala::Facebook::AuthenticationError + @inbox.channel.authorization_error! + raise rescue StandardError => e result = {} - Raven.capture_exception(e) + Sentry.capture_exception(e) end { name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}", diff --git a/app/controllers/api/v1/accounts/base_controller.rb b/app/controllers/api/v1/accounts/base_controller.rb index ef52206db..ddb0f44f4 100644 --- a/app/controllers/api/v1/accounts/base_controller.rb +++ b/app/controllers/api/v1/accounts/base_controller.rb @@ -14,7 +14,7 @@ class Api::V1::Accounts::BaseController < Api::BaseController account = Account.find(params[:account_id]) if current_user account_accessible_for_user?(account) - elsif @resource&.is_a?(AgentBot) + elsif @resource.is_a?(AgentBot) account_accessible_for_bot?(account) end account diff --git a/app/controllers/api/v1/accounts/callbacks_controller.rb b/app/controllers/api/v1/accounts/callbacks_controller.rb index 9a4cd85e8..07a6b4d71 100644 --- a/app/controllers/api/v1/accounts/callbacks_controller.rb +++ b/app/controllers/api/v1/accounts/callbacks_controller.rb @@ -69,39 +69,15 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController return [] if data.empty? data.inject([]) do |result, page_detail| - page_detail[:exists] = Current.account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false + page_detail[:exists] = Current.account.facebook_pages.exists?(page_id: page_detail['id']) result << page_detail end end def set_avatar(facebook_inbox, page_id) - uri = get_avatar_url(page_id) - - return unless uri - - avatar_resource = LocalResource.new(uri) - facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding) - rescue *ExceptionList::URI_EXCEPTIONS => e - Rails.logger.info "invalid url #{file_url} : #{e.message}" - end - - def get_avatar_url(page_id) - begin - url = 'http://graph.facebook.com/' << page_id << '/picture?type=large' - uri = URI.parse(url) - tries = 3 - begin - response = uri.open(redirect: false) - rescue OpenURI::HTTPRedirect => e - uri = e.uri # assigned from the "Location" response header - retry if (tries -= 1).positive? - raise - end - pic_url = response.base_uri.to_s - rescue StandardError => e - Rails.logger.debug "Rescued: #{e.inspect}" - pic_url = nil - end - pic_url + avatar_file = Down.download( + "http://graph.facebook.com/#{page_id}/picture?type=large" + ) + facebook_inbox.avatar.attach(io: avatar_file, filename: avatar_file.original_filename, content_type: avatar_file.content_type) end end diff --git a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb index ffbc78eb5..a53650e65 100644 --- a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb +++ b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb @@ -6,9 +6,8 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts: authenticate_twilio build_inbox setup_webhooks if @twilio_channel.sms? - rescue Twilio::REST::TwilioError => e - render_could_not_create_error(e.message) rescue StandardError => e + Sentry.capture_exception(e) render_could_not_create_error(e.message) end end diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 6ffa15172..b9b9ffafe 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -11,6 +11,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController before_action :check_authorization before_action :set_current_page, only: [:index, :active, :search] before_action :fetch_contact, only: [:show, :update, :contactable_inboxes] + before_action :set_include_contact_inboxes, only: [:index, :search] def index @contacts_count = resolved_contacts.count @@ -21,7 +22,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return contacts = resolved_contacts.where( - 'name ILIKE :search OR email ILIKE :search OR phone_number ILIKE :search', + 'name ILIKE :search OR email ILIKE :search OR phone_number ILIKE :search OR contacts.identifier LIKE :search', search: "%#{params[:q]}%" ) @contacts_count = contacts.count @@ -87,11 +88,15 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController end def fetch_contacts_with_conversation_count(contacts) - filtrate(contacts).left_outer_joins(:conversations) - .select('contacts.*, COUNT(conversations.id) as conversations_count') - .group('contacts.id') - .includes([{ avatar_attachment: [:blob] }, { contact_inboxes: [:inbox] }]) - .page(@current_page).per(RESULTS_PER_PAGE) + contacts_with_conversation_count = filtrate(contacts).left_outer_joins(:conversations) + .select('contacts.*, COUNT(conversations.id) as conversations_count') + .group('contacts.id') + .includes([{ avatar_attachment: [:blob] }]) + .page(@current_page).per(RESULTS_PER_PAGE) + + return contacts_with_conversation_count.includes([{ contact_inboxes: [:inbox] }]) if @include_contact_inboxes + + contacts_with_conversation_count end def build_contact_inbox @@ -103,7 +108,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController end def contact_params - params.require(:contact).permit(:name, :email, :phone_number, additional_attributes: {}, custom_attributes: {}) + params.require(:contact).permit(:name, :identifier, :email, :phone_number, additional_attributes: {}, custom_attributes: {}) end def contact_custom_attributes @@ -117,6 +122,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController contact_params.except(:custom_attributes).merge({ custom_attributes: contact_custom_attributes }) end + def set_include_contact_inboxes + @include_contact_inboxes = if params[:include_contact_inboxes].present? + params[:include_contact_inboxes] == 'true' + else + true + end + end + def fetch_contact @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id]) end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 9f4f0b45d..d360723a6 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -1,5 +1,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController include Events::Types + include DateRangeHelper before_action :conversation, except: [:index, :meta, :search, :create] before_action :contact_inbox, only: [:create] @@ -49,8 +50,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro def toggle_status if params[:status] - @conversation.status = params[:status] - @status = @conversation.save + set_conversation_status + @status = @conversation.save! else @status = @conversation.toggle_status end @@ -73,6 +74,12 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro private + def set_conversation_status + status = params[:status] == 'bot' ? 'pending' : params[:status] + @conversation.status = status + @conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until] + end + def trigger_typing_event(event) user = current_user.presence || @resource Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user) @@ -106,12 +113,16 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro def conversation_params additional_attributes = params[:additional_attributes]&.permit! || {} status = params[:status].present? ? { status: params[:status] } : {} + + # TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases + status = { status: 'pending' } if status[:status] == 'bot' { account_id: Current.account.id, inbox_id: @contact_inbox.inbox_id, contact_id: @contact_inbox.contact_id, contact_inbox_id: @contact_inbox.id, - additional_attributes: additional_attributes + additional_attributes: additional_attributes, + snoozed_until: params[:snoozed_until] }.merge(status) end diff --git a/app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb b/app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb new file mode 100644 index 000000000..687da55bd --- /dev/null +++ b/app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb @@ -0,0 +1,50 @@ +class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Accounts::BaseController + before_action :fetch_custom_attributes_definitions, except: [:create] + before_action :fetch_custom_attribute_definition, only: [:show, :update, :destroy] + DEFAULT_ATTRIBUTE_MODEL = 'conversation_attribute'.freeze + + def index; end + + def show; end + + def create + @custom_attribute_definition = Current.account.custom_attribute_definitions.create!( + permitted_payload + ) + end + + def update + @custom_attribute_definition.update!(permitted_payload) + end + + def destroy + @custom_attribute_definition.destroy + head :no_content + end + + private + + def fetch_custom_attributes_definitions + @custom_attribute_definitions = Current.account.custom_attribute_definitions.where( + attribute_model: permitted_params[:attribute_model] || DEFAULT_ATTRIBUTE_MODEL + ) + end + + def fetch_custom_attribute_definition + @custom_attribute_definition = @custom_attribute_definitions.find(permitted_params[:id]) + end + + def permitted_payload + params.require(:custom_attribute_definition).permit( + :attribute_display_name, + :attribute_display_type, + :attribute_key, + :attribute_model, + :default_value + ) + end + + def permitted_params + params.permit(:id, :filter_type) + end +end diff --git a/app/controllers/api/v1/accounts/inbox_members_controller.rb b/app/controllers/api/v1/accounts/inbox_members_controller.rb index 27e31c855..c5fb3486b 100644 --- a/app/controllers/api/v1/accounts/inbox_members_controller.rb +++ b/app/controllers/api/v1/accounts/inbox_members_controller.rb @@ -9,7 +9,7 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl update_agents_list head :ok rescue StandardError => e - Rails.logger.debug "Rescued: #{e.inspect}" + Rails.logger.debug { "Rescued: #{e.inspect}" } render_could_not_create_error('Could not add agents to inbox') end end diff --git a/app/controllers/api/v1/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb index b2c4949df..c96ae9a96 100644 --- a/app/controllers/api/v1/webhooks_controller.rb +++ b/app/controllers/api/v1/webhooks_controller.rb @@ -10,7 +10,7 @@ class Api::V1::WebhooksController < ApplicationController twitter_consumer.consume head :ok rescue StandardError => e - Raven.capture_exception(e) + Sentry.capture_exception(e) head :ok end diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb index 4c7dd4220..38c880526 100644 --- a/app/controllers/api/v1/widget/base_controller.rb +++ b/app/controllers/api/v1/widget/base_controller.rb @@ -94,6 +94,10 @@ class Api::V1::Widget::BaseController < ApplicationController { timestamp: permitted_params[:message][:timestamp] } end + def permitted_params + params.permit(:website_token) + end + def message_params { account_id: conversation.account_id, diff --git a/app/controllers/api/v1/widget/campaigns_controller.rb b/app/controllers/api/v1/widget/campaigns_controller.rb index fc26b18c8..cb9b96a38 100644 --- a/app/controllers/api/v1/widget/campaigns_controller.rb +++ b/app/controllers/api/v1/widget/campaigns_controller.rb @@ -4,10 +4,4 @@ class Api::V1::Widget::CampaignsController < Api::V1::Widget::BaseController def index @campaigns = @web_widget.inbox.campaigns.where(enabled: true) end - - private - - def permitted_params - params.permit(:website_token) - end end diff --git a/app/controllers/api/v1/widget/configs_controller.rb b/app/controllers/api/v1/widget/configs_controller.rb new file mode 100644 index 000000000..ac88c595a --- /dev/null +++ b/app/controllers/api/v1/widget/configs_controller.rb @@ -0,0 +1,41 @@ +class Api::V1::Widget::ConfigsController < Api::V1::Widget::BaseController + before_action :set_global_config + + def create + build_contact + set_token + end + + private + + def set_global_config + @global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL') + end + + def set_contact + @contact_inbox = @web_widget.inbox.contact_inboxes.find_by( + source_id: auth_token_params[:source_id] + ) + @contact = @contact_inbox&.contact + end + + def build_contact + return if @contact.present? + + @contact_inbox = @web_widget.create_contact_inbox(additional_attributes) + @contact = @contact_inbox.contact + end + + def set_token + payload = { source_id: @contact_inbox.source_id, inbox_id: @web_widget.inbox.id } + @token = ::Widget::TokenService.new(payload: payload).generate_token + end + + def additional_attributes + if @web_widget.inbox.account.feature_enabled?('ip_lookup') + { created_at_ip: request.remote_ip } + else + {} + end + end +end diff --git a/app/controllers/api/v1/widget/contacts_controller.rb b/app/controllers/api/v1/widget/contacts_controller.rb index 135d2ced3..8bc5cfd0b 100644 --- a/app/controllers/api/v1/widget/contacts_controller.rb +++ b/app/controllers/api/v1/widget/contacts_controller.rb @@ -29,6 +29,6 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController end def permitted_params - params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, custom_attributes: {}) + params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, :phone_number, custom_attributes: {}) end end diff --git a/app/controllers/api/v1/widget/inbox_members_controller.rb b/app/controllers/api/v1/widget/inbox_members_controller.rb index da7d6256b..c4bc377ea 100644 --- a/app/controllers/api/v1/widget/inbox_members_controller.rb +++ b/app/controllers/api/v1/widget/inbox_members_controller.rb @@ -4,10 +4,4 @@ class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController def index @inbox_members = @web_widget.inbox.inbox_members.includes(:user) end - - private - - def permitted_params - params.permit(:website_token) - end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9399771c3..d25f88269 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -16,7 +16,7 @@ class ApplicationController < ActionController::Base def handle_with_exception yield rescue ActiveRecord::RecordNotFound => e - Raven.capture_exception(e) + Sentry.capture_exception(e) render_not_found_error('Resource could not be found') rescue Pundit::NotAuthorizedError render_unauthorized('You are not authorized to do this action') diff --git a/app/controllers/concerns/auth_helper.rb b/app/controllers/concerns/auth_helper.rb index 2000276f0..f56be37ef 100644 --- a/app/controllers/concerns/auth_helper.rb +++ b/app/controllers/concerns/auth_helper.rb @@ -1,8 +1,8 @@ module AuthHelper def send_auth_headers(user) data = user.create_new_auth_token - response.headers[DeviseTokenAuth.headers_names[:"access-token"]] = data['access-token'] - response.headers[DeviseTokenAuth.headers_names[:"token-type"]] = 'Bearer' + response.headers[DeviseTokenAuth.headers_names[:'access-token']] = data['access-token'] + response.headers[DeviseTokenAuth.headers_names[:'token-type']] = 'Bearer' response.headers[DeviseTokenAuth.headers_names[:client]] = data['client'] response.headers[DeviseTokenAuth.headers_names[:expiry]] = data['expiry'] response.headers[DeviseTokenAuth.headers_names[:uid]] = data['uid'] diff --git a/app/controllers/public/api/v1/csat_survey_controller.rb b/app/controllers/public/api/v1/csat_survey_controller.rb new file mode 100644 index 000000000..b839bee77 --- /dev/null +++ b/app/controllers/public/api/v1/csat_survey_controller.rb @@ -0,0 +1,32 @@ +class Public::Api::V1::CsatSurveyController < PublicController + before_action :set_conversation + before_action :set_message + + def show; end + + def update + render json: { error: 'You cannot update the CSAT survey after 14 days' }, status: :unprocessable_entity and return if check_csat_locked + + @message.update!(message_update_params[:message]) + end + + private + + def set_conversation + return if params[:id].blank? + + @conversation = Conversation.find_by!(uuid: params[:id]) + end + + def set_message + @message = @conversation.messages.find_by!(content_type: 'input_csat') + end + + def message_update_params + params.permit(message: [{ submitted_values: [:name, :title, :value, { csat_survey_response: [:feedback_message, :rating] }] }]) + end + + def check_csat_locked + (Time.zone.now.to_date - @message.created_at.to_date).to_i > 14 + end +end diff --git a/app/controllers/public/api/v1/inboxes/messages_controller.rb b/app/controllers/public/api/v1/inboxes/messages_controller.rb index 65a21183a..68c0f5223 100644 --- a/app/controllers/public/api/v1/inboxes/messages_controller.rb +++ b/app/controllers/public/api/v1/inboxes/messages_controller.rb @@ -14,7 +14,7 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon def update @message.update!(message_update_params) rescue StandardError => e - render json: { error: @contact.errors, message: e.message }.to_json, status: 500 + render json: { error: @contact.errors, message: e.message }.to_json, status: :internal_server_error end private diff --git a/app/controllers/survey/responses_controller.rb b/app/controllers/survey/responses_controller.rb new file mode 100644 index 000000000..8bbd0fe88 --- /dev/null +++ b/app/controllers/survey/responses_controller.rb @@ -0,0 +1,10 @@ +class Survey::ResponsesController < ActionController::Base + before_action :set_global_config + def show; end + + private + + def set_global_config + @global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL') + end +end diff --git a/app/controllers/swagger_controller.rb b/app/controllers/swagger_controller.rb index c5f8c0f5b..23a87b757 100644 --- a/app/controllers/swagger_controller.rb +++ b/app/controllers/swagger_controller.rb @@ -3,7 +3,7 @@ class SwaggerController < ApplicationController if Rails.env.development? || Rails.env.test? render inline: File.read(Rails.root.join('swagger', derived_path)) else - head 404 + head :not_found end end diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb index 44a23568e..f19a24888 100644 --- a/app/controllers/widgets_controller.rb +++ b/app/controllers/widgets_controller.rb @@ -1,3 +1,4 @@ +# TODO : Delete this and associated spec once 'api/widget/config' end point is merged class WidgetsController < ActionController::Base before_action :set_global_config before_action :set_web_widget diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index 66602988a..63846acaa 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -114,13 +114,13 @@ class ConversationFinder end def current_page - params[:page] + params[:page] || 1 end def conversations @conversations = @conversations.includes( - :taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } } + :taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team ) - current_page ? @conversations.latest.page(current_page) : @conversations.latest + @conversations.latest.page(current_page) end end diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue index 2d196af04..147e1d525 100644 --- a/app/javascript/dashboard/App.vue +++ b/app/javascript/dashboard/App.vue @@ -3,12 +3,17 @@ + + diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue b/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue index c19d0c50f..52c1ede28 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue @@ -26,6 +26,16 @@ Contact Support + + + {{ $t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS') }} + + + + diff --git a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue index ab4f5b5f1..d00f01667 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue @@ -57,6 +57,7 @@ export default { value: { type: String, default: '' }, placeholder: { type: String, default: '' }, isPrivate: { type: Boolean, default: false }, + isFormatMode: { type: Boolean, default: false }, }, data() { return { @@ -139,8 +140,17 @@ export default { value(newValue = '') { if (newValue !== this.lastValue) { const { tr } = this.state; - tr.insertText(newValue, 0, tr.doc.content.size); - this.state = this.view.state.apply(tr); + if (this.isFormatMode) { + this.state = createState( + newValue, + this.placeholder, + this.plugins, + this.isFormatMode + ); + } else { + tr.insertText(newValue, 0, tr.doc.content.size); + this.state = this.view.state.apply(tr); + } this.view.updateState(this.state); } }, @@ -271,4 +281,25 @@ export default { padding: 0 var(--space-smaller); } } + +.editor-wrap { + margin-bottom: var(--space-normal); +} + +.message-editor { + border: 1px solid var(--color-border); + border-radius: var(--border-radius-normal); + padding: 0 var(--space-slab); + margin-bottom: 0; +} + +.editor_warning { + border: 1px solid var(--r-400); +} + +.editor-warning__message { + color: var(--r-400); + font-weight: var(--font-weight-normal); + padding: var(--space-smaller) 0 0 0; +} diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index 65bcdc74f..1ffb09e04 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -78,11 +78,17 @@ diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index 58ef6e216..3b716d9eb 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -2,6 +2,11 @@
  • +
    @@ -79,16 +85,19 @@ import copy from 'copy-text-to-clipboard'; import messageFormatterMixin from 'shared/mixins/messageFormatterMixin'; import timeMixin from '../../../mixins/time'; + +import BubbleMailHead from './bubble/MailHead'; import BubbleText from './bubble/Text'; import BubbleImage from './bubble/Image'; import BubbleFile from './bubble/File'; +import BubbleActions from './bubble/Actions'; + import Spinner from 'shared/components/Spinner'; import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu'; import { isEmptyObject } from 'dashboard/helper/commons'; import alertMixin from 'shared/mixins/alertMixin'; import contentTypeMixin from 'shared/mixins/contentTypeMixin'; -import BubbleActions from './bubble/Actions'; import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages'; import { generateBotMessageContent } from './helpers/botMessageContentHelper'; @@ -98,6 +107,7 @@ export default { BubbleText, BubbleImage, BubbleFile, + BubbleMailHead, ContextMenu, Spinner, }, @@ -133,11 +143,11 @@ export default { const { email: { + content_type: contentType = '', html_content: { full: fullHTMLContent, reply: replyHTMLContent } = {}, text_content: { full: fullTextContent, reply: replyTextContent } = {}, } = {}, } = this.contentAttributes; - let contentToBeParsed = replyHTMLContent || replyTextContent || @@ -147,7 +157,12 @@ export default { if (contentToBeParsed && this.isIncoming) { const parsedContent = this.stripStyleCharacters(contentToBeParsed); if (parsedContent) { - return parsedContent; + // This is a temporary fix for line-breaks in text/plain emails + // Now, It is not rendered properly in the email preview. + // FIXME: Remove this once we have a better solution for rendering text/plain emails + return contentType.includes('text/plain') + ? parsedContent.replace(/\n/g, '
    ') + : parsedContent; } } return ( diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 9b4de3651..9b2c39996 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -33,11 +33,11 @@
    - @@ -207,10 +208,10 @@ export default { selectedTweet() { if (this.selectedTweetId) { const { messages = [] } = this.getMessages; - const [selectedMessage = {}] = messages.filter( + const [selectedMessage] = messages.filter( message => message.id === this.selectedTweetId ); - return selectedMessage.content || ''; + return selectedMessage || {}; } return ''; }, @@ -233,7 +234,7 @@ export default { created() { bus.$on('scrollToMessage', () => { - setTimeout(() => this.scrollToBottom(), 0); + this.$nextTick(() => this.scrollToBottom()); this.makeMessagesRead(); }); @@ -255,7 +256,7 @@ export default { this.conversationPanel = this.$el.querySelector('.conversation-panel'); this.setScrollParams(); this.conversationPanel.addEventListener('scroll', this.handleScroll); - this.scrollToBottom(); + this.$nextTick(() => this.scrollToBottom()); this.isLoadingPrevious = false; }, removeScrollListener() { diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 5d97076bb..6e7a039af 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -74,6 +74,7 @@ import { mapGetters } from 'vuex'; import { mixin as clickaway } from 'vue-clickaway'; import alertMixin from 'shared/mixins/alertMixin'; +import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import EmojiInput from 'shared/components/emoji/EmojiInput'; import CannedResponse from './CannedResponse'; @@ -105,11 +106,21 @@ export default { ReplyBottomPanel, WootMessageEditor, }, - mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin], + mixins: [ + clickaway, + inboxMixin, + uiSettingsMixin, + alertMixin, + eventListenerMixins, + ], props: { - inReplyTo: { - type: [String, Number], - default: '', + selectedTweet: { + type: [Object, String], + default: () => ({}), + }, + isATweet: { + type: Boolean, + default: false, }, }, data() { @@ -169,11 +180,14 @@ export default { return this.maxLength - this.message.length; }, isReplyButtonDisabled() { - const isMessageEmpty = !this.message.trim().replace(/\n/g, '').length; + if (this.isATweet && !this.inReplyTo) { + return true; + } if (this.hasAttachments) return false; + return ( - isMessageEmpty || + this.isMessageEmpty || this.message.length === 0 || this.message.length > this.maxLength ); @@ -198,7 +212,7 @@ export default { } if (this.isATwitterInbox) { if (this.conversationType === 'tweet') { - return MESSAGE_MAX_LENGTH.TWEET; + return MESSAGE_MAX_LENGTH.TWEET - this.replyToUserLength - 2; } } return MESSAGE_MAX_LENGTH.GENERAL; @@ -235,6 +249,25 @@ export default { isOnPrivateNote() { return this.replyType === REPLY_EDITOR_MODES.NOTE; }, + inReplyTo() { + const selectedTweet = this.selectedTweet || {}; + return selectedTweet.id; + }, + replyToUserLength() { + const selectedTweet = this.selectedTweet || {}; + const { + sender: { + additional_attributes: { screen_name: screenName = '' } = {}, + } = {}, + } = selectedTweet; + return screenName ? screenName.length : 0; + }, + isMessageEmpty() { + if (!this.message) { + return true; + } + return !this.message.trim().replace(/\n/g, '').length; + }, }, watch: { currentChat(conversation) { @@ -263,12 +296,6 @@ export default { } }, }, - mounted() { - document.addEventListener('keydown', this.handleKeyEvents); - }, - destroyed() { - document.removeEventListener('keydown', this.handleKeyEvents); - }, methods: { toggleUserMention(currentMentionState) { this.hasUserMention = currentMentionState; @@ -327,6 +354,9 @@ export default { if (this.showRichContentEditor) { return; } + if (this.$refs.messageInput === undefined) { + return; + } this.$nextTick(() => this.$refs.messageInput.focus()); }, emojiOnClick(emoji) { diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyEmailHead.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyEmailHead.vue new file mode 100644 index 000000000..4f30f03bd --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyEmailHead.vue @@ -0,0 +1,119 @@ + + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue index 8c6954672..59a0a9896 100644 --- a/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue +++ b/app/javascript/dashboard/components/widgets/conversation/bubble/Actions.vue @@ -14,13 +14,13 @@ @mouseleave="isHovered = false" /> +
    +
    + {{ $t('EMAIL_HEADER.TO') }}: + {{ toMails }} +
    +
    + {{ $t('EMAIL_HEADER.CC') }}: + {{ ccMails }} +
    +
    + {{ $t('EMAIL_HEADER.BCC') }}: + {{ bccMails }} +
    +
    + + {{ $t('EMAIL_HEADER.SUBJECT') }}: + + {{ subject }} +
    +
    + + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/emailHeadHelper.js b/app/javascript/dashboard/components/widgets/conversation/helpers/emailHeadHelper.js new file mode 100644 index 000000000..ad36393a7 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/helpers/emailHeadHelper.js @@ -0,0 +1,7 @@ +import emailValidator from 'vuelidate/lib/validators/email'; + +export const validEmailsByComma = value => { + if (!value.length) return true; + const emails = value.split(','); + return emails.every(email => emailValidator(email)); +}; diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/specs/emailHeadHelper.spec.js b/app/javascript/dashboard/components/widgets/conversation/helpers/specs/emailHeadHelper.spec.js new file mode 100644 index 000000000..2275a810b --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/helpers/specs/emailHeadHelper.spec.js @@ -0,0 +1,13 @@ +import { validEmailsByComma } from '../emailHeadHelper'; + +describe('#validEmailsByComma', () => { + it('returns true when empty string is passed', () => { + expect(validEmailsByComma('')).toEqual(true); + }); + it('returns true when valid emails separated by comma is passed', () => { + expect(validEmailsByComma('ni@njan.com,po@va.da')).toEqual(true); + }); + it('returns false when one of the email passed is invalid', () => { + expect(validEmailsByComma('ni@njan.com,pova.da')).toEqual(false); + }); +}); diff --git a/app/javascript/dashboard/components/widgets/conversation/stories/ReplyEmailHead.stories.js b/app/javascript/dashboard/components/widgets/conversation/stories/ReplyEmailHead.stories.js new file mode 100644 index 000000000..a21a66874 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/stories/ReplyEmailHead.stories.js @@ -0,0 +1,27 @@ +import ReplyEmailHead from '../ReplyEmailHead'; + +export default { + title: 'Components/ReplyBox/EmailHead', + component: ReplyEmailHead, + argTypes: { + ccEmails: { + control: { + type: 'string', + }, + }, + bccEmails: { + control: { + type: 'string', + }, + }, + }, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { ReplyEmailHead }, + template: + '', +}); + +export const Add = Template.bind({}); diff --git a/app/javascript/dashboard/components/widgets/modal/WootKeyShortcutModal.stories.js b/app/javascript/dashboard/components/widgets/modal/WootKeyShortcutModal.stories.js new file mode 100644 index 000000000..2767018c0 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/modal/WootKeyShortcutModal.stories.js @@ -0,0 +1,17 @@ +import WootKeyboardShortcutModal from './WootKeyShortcutModal.vue'; + +export default { + title: 'Components/Shortcuts/Keyboard Shortcut', + component: WootKeyboardShortcutModal, + argTypes: {}, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { WootKeyboardShortcutModal }, + template: + '', +}); + +export const KeyboardShortcut = Template.bind({}); +KeyboardShortcut.args = {}; diff --git a/app/javascript/dashboard/components/widgets/modal/WootKeyShortcutModal.vue b/app/javascript/dashboard/components/widgets/modal/WootKeyShortcutModal.vue new file mode 100644 index 000000000..fb4bef960 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/modal/WootKeyShortcutModal.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/app/javascript/dashboard/components/widgets/modal/constants.js b/app/javascript/dashboard/components/widgets/modal/constants.js new file mode 100644 index 000000000..faec60dc1 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/modal/constants.js @@ -0,0 +1,86 @@ +export const SHORTCUT_KEYS = [ + { + id: 1, + label: 'NAVIGATE_DROPDOWN', + firstkey: 'Up', + secondKey: 'Down', + }, + { + id: 2, + label: 'RESOLVE_CONVERSATION', + firstkey: 'Alt / ⌥', + secondKey: 'E', + }, + { + id: 3, + label: 'GO_TO_CONVERSATION_DASHBOARD', + firstkey: 'Alt / ⌥', + secondKey: 'C', + }, + { + id: 4, + label: 'ADD_ATTACHMENT', + firstkey: 'Alt / ⌥', + secondKey: 'A', + }, + { + id: 5, + label: 'GO_TO_CONTACTS_DASHBOARD', + firstkey: 'Alt / ⌥', + secondKey: 'V', + }, + { + id: 6, + label: 'TOGGLE_SIDEBAR', + firstkey: 'Alt / ⌥', + secondKey: 'O', + }, + { + id: 7, + label: 'GO_TO_REPORTS_SIDEBAR', + firstkey: 'Alt / ⌥', + secondKey: 'R', + }, + { + id: 8, + label: 'MOVE_TO_NEXT_TAB', + firstkey: 'Alt / ⌥', + secondKey: 'N', + }, + { + id: 9, + label: 'GO_TO_SETTINGS', + firstkey: 'Alt / ⌥', + secondKey: 'S', + }, + { + id: 10, + label: 'SWITCH_CONVERSATION_STATUS', + firstkey: 'Alt / ⌥', + secondKey: 'B', + }, + { + id: 11, + label: 'SWITCH_TO_PRIVATE_NOTE', + firstkey: 'Alt / ⌥', + secondKey: 'P', + }, + { + id: 12, + label: 'TOGGLE_RICH_CONTENT_EDITOR', + firstkey: 'Alt / ⌥', + secondKey: 'W', + }, + { + id: 13, + label: 'SWITCH_TO_REPLY', + firstkey: 'Alt / ⌥', + secondKey: 'L', + }, + { + id: 14, + label: 'TOGGLE_SNOOZE_DROPDOWN', + firstkey: 'Alt / ⌥', + secondKey: 'M', + }, +]; diff --git a/app/javascript/dashboard/constants.js b/app/javascript/dashboard/constants.js index 34f4b9845..58fdabdc1 100644 --- a/app/javascript/dashboard/constants.js +++ b/app/javascript/dashboard/constants.js @@ -8,7 +8,8 @@ export default { STATUS_TYPE: { OPEN: 'open', RESOLVED: 'resolved', - BOT: 'bot', + PENDING: 'pending', + SNOOZED: 'snoozed', }, }; export const DEFAULT_REDIRECT_URL = '/app/'; diff --git a/app/javascript/dashboard/i18n/default-sidebar.js b/app/javascript/dashboard/i18n/default-sidebar.js index 03d93b491..75a0675cc 100644 --- a/app/javascript/dashboard/i18n/default-sidebar.js +++ b/app/javascript/dashboard/i18n/default-sidebar.js @@ -47,6 +47,13 @@ export const getSidebarItems = accountId => ({ toState: frontendURL(`accounts/${accountId}/reports`), toStateName: 'settings_account_reports', }, + campaigns: { + icon: 'ion-speakerphone', + label: 'CAMPAIGNS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/campaigns`), + toStateName: 'settings_account_campaigns', + }, settings: { icon: 'ion-settings', label: 'SETTINGS', @@ -105,6 +112,32 @@ export const getSidebarItems = accountId => ({ }, }, }, + campaigns: { + routes: ['settings_account_campaigns', 'one_off'], + menuItems: { + back: { + icon: 'ion-ios-arrow-back', + label: 'HOME', + hasSubMenu: false, + toStateName: 'home', + toState: frontendURL(`accounts/${accountId}/dashboard`), + }, + ongoingCampaigns: { + icon: 'ion-arrow-swap', + label: 'ONGOING', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`), + toStateName: 'settings_account_campaigns', + }, + onOffCampaigns: { + icon: 'ion-radio-waves', + label: 'ONE_OFF', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/campaigns/one_off`), + toStateName: 'one_off', + }, + }, + }, settings: { routes: [ 'agent_list', diff --git a/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json index d6fd9f086..fd94f2f3c 100644 --- a/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ar/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل", "AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.", "HMAC_VERIFICATION": "التحقق من هوية المستخدم", - "HMAC_DESCRIPTION": "للتحقق من هوية المستخدمين، يسمح لك SDK بتمرير 'identity_hash' لكل مستخدم. يمكنك إنشاء HMAC باستخدام 'sha256' مع المفتاح المعروض هنا." + "HMAC_DESCRIPTION": "للتحقق من هوية المستخدمين، يسمح لك SDK بتمرير 'identifier_hash' لكل مستخدم. يمكنك إنشاء HMAC باستخدام 'sha256' مع المفتاح المعروض هنا." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "إعادة التصريح", diff --git a/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json index 2dc3d12f5..6c38c1283 100644 --- a/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ca/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Actualitza la configuració de la safata d'entrada", "AUTO_ASSIGNMENT_SUB_TEXT": "Activa o desactiva l'assignació automàtica d'agents disponibles a les noves converses", "HMAC_VERIFICATION": "Validació de la Identitat del Usuari", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reautoritza", diff --git a/app/javascript/dashboard/i18n/locale/cs/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/cs/inboxMgmt.json index a65d5d056..ee1b94460 100644 --- a/app/javascript/dashboard/i18n/locale/cs/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/cs/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Aktualizujte nastavení doručené pošty", "AUTO_ASSIGNMENT_SUB_TEXT": "Povolit nebo zakázat automatické přiřazování nových konverzací agentům přidaným do této schránky.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Znovu autorizovat", diff --git a/app/javascript/dashboard/i18n/locale/da/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/da/inboxMgmt.json index 2a0f78297..6b9680508 100644 --- a/app/javascript/dashboard/i18n/locale/da/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/da/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Opdater dine indbakkeindstillinger", "AUTO_ASSIGNMENT_SUB_TEXT": "Aktiver eller deaktiver automatisk tildeling af nye samtaler til agenter tilføjet til denne indbakke.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Genautorisér", diff --git a/app/javascript/dashboard/i18n/locale/de/conversation.json b/app/javascript/dashboard/i18n/locale/de/conversation.json index 0290d66a1..14cb3b439 100644 --- a/app/javascript/dashboard/i18n/locale/de/conversation.json +++ b/app/javascript/dashboard/i18n/locale/de/conversation.json @@ -9,6 +9,7 @@ "SEARCH_MESSAGES": "Nachrichten durchsuchen", "SEARCH": { "TITLE": "Nachrichten durchsuchen", + "RESULT_TITLE": "Suchergebnisse", "LOADING_MESSAGE": "Daten werden geladen...", "PLACEHOLDER": "Geben Sie einen Text ein, um danach zu suchen", "NO_MATCHING_RESULTS": "Keine Ergebnisse gefunden." diff --git a/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json index 63d603b08..d35caea2a 100644 --- a/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/de/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Posteingangseinstellungen aktualisieren", "AUTO_ASSIGNMENT_SUB_TEXT": "Aktivieren oder deaktivieren Sie die automatische Zuweisung verfügbarer Agenten für neue Konversationen", "HMAC_VERIFICATION": "Benutzeridentitätsüberprüfung", - "HMAC_DESCRIPTION": "Um die Benutzer-Identität zu validieren, kannst du einen `identity_hash` für jeden Benutzer übergeben. Du kannst den Hash mithilfe des 'sha256' Verfahrens generieren, der notwendige Schlüssel wird hier angezeigt." + "HMAC_DESCRIPTION": "Um die Benutzer-Identität zu validieren, kannst du einen `identifier_hash` für jeden Benutzer übergeben. Du kannst den Hash mithilfe des 'sha256' Verfahrens generieren, der notwendige Schlüssel wird hier angezeigt." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Neu autorisieren", diff --git a/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json index 5e0e38abd..f7a1d6826 100644 --- a/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Ενημερώστε τις ρυθμίσεις του κιβωτίου σας", "AUTO_ASSIGNMENT_SUB_TEXT": "Ενεργοποιήστε ή απενεργοποιήστε την αυτόματη αντιστοίχιση των νέων συζητήσεων στους πράκτορες αυτού του κιβωτίου.", "HMAC_VERIFICATION": "Επικύρωση Ταυτότητας Χρήστη", - "HMAC_DESCRIPTION": "Για λόγους επικύρωσης της ταυτότητας των χρηστών, το SDK σας επιτρέπει να περάσετε ένα `identity_hash` για κάθε χρήστη. Μπορείτε να δημιουργήσετε HMAC χρησιμοποιώντας το 'sha256' με το κλειδί που εμφανίζεται εδώ." + "HMAC_DESCRIPTION": "Για λόγους επικύρωσης της ταυτότητας των χρηστών, το SDK σας επιτρέπει να περάσετε ένα `identifier_hash` για κάθε χρήστη. Μπορείτε να δημιουργήσετε HMAC χρησιμοποιώντας το 'sha256' με το κλειδί που εμφανίζεται εδώ." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Εκ νέου εξουσιοδότηση", diff --git a/app/javascript/dashboard/i18n/locale/en/agentMgmt.json b/app/javascript/dashboard/i18n/locale/en/agentMgmt.json index dcc8c9fda..0f965c717 100644 --- a/app/javascript/dashboard/i18n/locale/en/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/agentMgmt.json @@ -91,6 +91,23 @@ }, "SEARCH": { "NO_RESULTS": "No results found." + }, + "MULTI_SELECTOR": { + "PLACEHOLDER": "None", + "TITLE": { + "AGENT": "Select agent", + "TEAM": "Select team" + }, + "SEARCH": { + "NO_RESULTS": { + "AGENT": "No agents found", + "TEAM": "No teams found" + }, + "PLACEHOLDER": { + "AGENT": "Search agents", + "TEAM": "Search teams" + } + } } } } diff --git a/app/javascript/dashboard/i18n/locale/en/campaign.json b/app/javascript/dashboard/i18n/locale/en/campaign.json index 95ed4d199..a348f2bb3 100644 --- a/app/javascript/dashboard/i18n/locale/en/campaign.json +++ b/app/javascript/dashboard/i18n/locale/en/campaign.json @@ -2,7 +2,10 @@ "CAMPAIGN": { "HEADER": "Campaigns", "SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on Add Campaign to create a new campaign. You can also edit or delete an existing campaign by clicking on the Edit or Delete button.", - "HEADER_BTN_TXT": "Create a campaign", + "HEADER_BTN_TXT": { + "ONE_OFF": "Create a one off campaign", + "ONGOING": "Create a ongoing campaign" + }, "ADD": { "TITLE": "Create a campaign", "DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.", @@ -25,6 +28,11 @@ "PLACEHOLDER": "Select the customer labels", "ERROR": "Audience is required" }, + "INBOX": { + "LABEL": "Select Inbox", + "PLACEHOLDER": "Select Inbox", + "ERROR": "Inbox is required" + }, "MESSAGE": { "LABEL": "Message", "PLACEHOLDER": "Please enter the message of campaign", @@ -80,6 +88,7 @@ "TABLE_HEADER": { "TITLE": "Title", "MESSAGE": "Message", + "INBOX": "Inbox", "STATUS": "Status", "SENDER": "Sender", "URL": "URL", @@ -101,6 +110,16 @@ "SENDER": { "BOT": "Bot" } + }, + "ONE_OFF": { + "HEADER": "One off campaigns", + "404": "There are no one off campaigns created", + "INBOXES_NOT_FOUND": "Please create an sms inbox and start adding campaigns" + }, + "ONGOING": { + "HEADER": "Ongoing campaigns", + "404": "There are no ongoing campaigns created", + "INBOXES_NOT_FOUND": "Please create an website inbox and start adding campaigns" } } } diff --git a/app/javascript/dashboard/i18n/locale/en/chatlist.json b/app/javascript/dashboard/i18n/locale/en/chatlist.json index ef4e0629a..f30c89196 100644 --- a/app/javascript/dashboard/i18n/locale/en/chatlist.json +++ b/app/javascript/dashboard/i18n/locale/en/chatlist.json @@ -47,8 +47,12 @@ "VALUE": "resolved" }, { - "TEXT": "Bot", - "VALUE": "bot" + "TEXT": "Pending", + "VALUE": "pending" + }, + { + "TEXT": "Snoozed", + "VALUE": "snoozed" } ], "ATTACHMENTS": { diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 81c023aa5..ef7a66691 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -9,6 +9,7 @@ "SEARCH_MESSAGES": "Search for messages in conversations", "SEARCH": { "TITLE": "Search messages", + "RESULT_TITLE": "Search Results", "LOADING_MESSAGE": "Crunching data...", "PLACEHOLDER": "Type any text to search messages", "NO_MATCHING_RESULTS": "No results found." @@ -22,7 +23,7 @@ "24_HOURS_WINDOW": "24 hour message window restriction", "TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to", "TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction", - "LAST_INCOMING_TWEET": "You are replying to the last incoming tweet", + "SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.", "REPLYING_TO": "You are replying to:", "REMOVE_SELECTION": "Remove Selection", "DOWNLOAD": "Download", @@ -41,7 +42,13 @@ "DETAILS": "details" }, "RESOLVE_DROPDOWN": { - "OPEN_BOT": "Open with bot" + "MARK_PENDING": "Mark as pending", + "SNOOZE": { + "TITLE": "Snooze until", + "NEXT_REPLY": "Next reply", + "TOMORROW": "Tomorrow", + "NEXT_WEEK": "Next week" + } }, "FOOTER": { "MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.", @@ -57,7 +64,20 @@ "TIP_EMOJI_ICON": "Show emoji selector", "TIP_ATTACH_ICON": "Attach files", "ENTER_TO_SEND": "Enter to send", - "DRAG_DROP": "Drag and drop here to attach" + "DRAG_DROP": "Drag and drop here to attach", + "EMAIL_HEAD": { + "ADD_BCC": "Add bcc", + "CC": { + "LABEL": "CC", + "PLACEHOLDER": "Emails separated by commas", + "ERROR": "Please enter valid email addresses" + }, + "BCC": { + "LABEL": "BCC", + "PLACEHOLDER": "Emails separated by commas", + "ERROR": "Please enter valid email addresses" + } + } }, "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", "CHANGE_STATUS": "Conversation status changed", @@ -123,5 +143,11 @@ "SELECT": { "PLACEHOLDER": "None" } + }, + "EMAIL_HEADER": { + "TO": "To", + "BCC": "Bcc", + "CC": "Cc", + "SUBJECT": "Subject" } } diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index aef8d1309..c58911903 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder to validate the user's identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reauthorize", diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 988f27923..6bbd35e32 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -98,6 +98,7 @@ "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", + "KEYBOARD_SHORTCUTS": "Keyboard Shortcuts", "LOGOUT": "Logout" }, "APP_GLOBAL": { @@ -126,8 +127,8 @@ "SIDEBAR": { "CONVERSATIONS": "Conversations", "REPORTS": "Reports", - "CONTACTS": "Contacts", "SETTINGS": "Settings", + "CONTACTS": "Contacts", "HOME": "Home", "AGENTS": "Agents", "INBOXES": "Inboxes", @@ -141,9 +142,13 @@ "ALL_CONTACTS": "All Contacts", "TAGGED_WITH": "Tagged with", "REPORTS_OVERVIEW": "Overview", - "CSAT": "CSAT" + "CSAT": "CSAT", + "CAMPAIGNS": "Campaigns", + "ONGOING": "Ongoing", + "ONE_OFF": "One off" }, "CREATE_ACCOUNT": { + "NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.", "NEW_ACCOUNT": "New Account", "SELECTOR_SUBTITLE": "Create a new account", "API": { @@ -158,5 +163,30 @@ }, "SUBMIT": "Submit" } + }, + "KEYBOARD_SHORTCUTS": { + "TITLE": { + "OPEN_CONVERSATION": "Open conversation", + "RESOLVE_AND_NEXT": "Resolve and move to next", + "NAVIGATE_DROPDOWN": "Navigate dropdown items", + "RESOLVE_CONVERSATION": "Resolve Conversation", + "GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard", + "ADD_ATTACHMENT": "Add Attachment", + "GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard", + "TOGGLE_SIDEBAR": "Toggle Sidebar", + "GO_TO_REPORTS_SIDEBAR": "Go to Reports sidebar", + "MOVE_TO_NEXT_TAB": "Move to next tab in conversation list", + "GO_TO_SETTINGS": "Go to Settings", + "SWITCH_CONVERSATION_STATUS": "Switch Conversation status", + "SWITCH_TO_PRIVATE_NOTE": "Switch to Private Note", + "TOGGLE_RICH_CONTENT_EDITOR": "Toggle Rich Content editor", + "SWITCH_TO_REPLY": "Switch to Reply", + "TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown" + }, + "KEYS": { + "COMMAND_KEY": "⌘", + "ALT_OR_OPTION_KEY": "Alt / ⌥", + "FORWARD_SLASH_KEY": "/" + } } } diff --git a/app/javascript/dashboard/i18n/locale/es/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/es/inboxMgmt.json index 9b7154c8c..f047213fa 100644 --- a/app/javascript/dashboard/i18n/locale/es/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/es/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Actualizar la configuración de tu bandeja de entrada", "AUTO_ASSIGNMENT_SUB_TEXT": "Activar o desactivar la asignación automática de nuevas conversaciones a los agentes añadidos a esta bandeja de entrada.", "HMAC_VERIFICATION": "Validación de identidad de usuario", - "HMAC_DESCRIPTION": "Con el fin de validar la identidad de los usuarios, el SDK le permite pasar un `identity_hash` por cada usuario. Puede generar HMAC usando 'sha256' con la clave que se muestra aquí." + "HMAC_DESCRIPTION": "Con el fin de validar la identidad de los usuarios, el SDK le permite pasar un `identifier_hash` por cada usuario. Puede generar HMAC usando 'sha256' con la clave que se muestra aquí." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reautorizar", diff --git a/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json b/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json index bec05d7e9..8d287eef8 100644 --- a/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json @@ -3,7 +3,7 @@ "HEADER": "Edustajat", "HEADER_BTN_TXT": "Lisää edustaja", "LOADING": "Haetaan Edustajalistaa", - "SIDEBAR_TXT": "

    Edustajat

    An Edustaja on jäsenenä asiakastukitiimissäsi.

    Edustajat voivat katsella ja vastata viesteihin asiakkailtasi. Luettelo näyttää kaikki edustajat, jotka ovat tällä hetkellä tililläsi.

    Klikkaa Lisää edustaja lisätäksesi uuden edustajan. Edustaja, jonka lisäät, saa sähköpostiviestin, jossa on vahvistuslinkki tilin aktivointiin, jonka jälkeen he voivat käyttää Chatwoot -sovellusta ja vastata viesteihin.

    Pääsy Chatwoot'n ominaisuuksiin perustuu seuraaviin rooleihin.

    Edustaja - Tällä roolilla toimivat edustajat voivat käyttää vain saapuneita, raportteja ja keskusteluja. He voivat määrittää keskusteluja muille edustajille tai itse ratkaista keskusteluja.

    Ylläpitäjä - Ylläpitäjällä on pääsy kaikkiin Chatwoot ominaisuuksiin, jotka ovat käytössä tililläsi, mukaan lukien asetukset sekä kaikki normaalien asiamiesten oikeudet.

    ", + "SIDEBAR_TXT": "

    Edustajat

    Edustaja on jäsenenä asiakastukitiimissäsi.

    Edustajat voivat katsella ja vastata viesteihin asiakkailtasi. Luettelo näyttää kaikki edustajat, jotka ovat tällä hetkellä tililläsi.

    Klikkaa Lisää edustaja lisätäksesi uuden edustajan. Edustaja, jonka lisäät, saa sähköpostiviestin, jossa on vahvistuslinkki tilin aktivointiin, jonka jälkeen he voivat käyttää Chatwoot -sovellusta ja vastata viesteihin.

    Pääsy Chatwoot'n ominaisuuksiin perustuu seuraaviin rooleihin.

    Edustaja - Tällä roolilla toimivat edustajat voivat käyttää vain saapuneita, raportteja ja keskusteluja. He voivat määrittää keskusteluja muille edustajille tai itse ratkaista keskusteluja.

    Ylläpitäjä - Ylläpitäjällä on pääsy kaikkiin Chatwoot ominaisuuksiin, jotka ovat käytössä tililläsi, mukaan lukien asetukset sekä kaikki normaalien asiamiesten oikeudet.

    ", "AGENT_TYPES": { "ADMINISTRATOR": "Ylläpitäjä", "AGENT": "Edustajat" @@ -29,9 +29,9 @@ "PLACEHOLDER": "Ole hyvä ja kirjoita edustajan nimi" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Edustajan tyyppi", + "PLACEHOLDER": "Valitse tyyppi", + "ERROR": "Edustajan tyyppi on pakollinen" }, "EMAIL": { "LABEL": "Sähköpostiosoite", @@ -66,9 +66,9 @@ "PLACEHOLDER": "Ole hyvä ja kirjoita edustajan nimi" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Edustajan tyyppi", + "PLACEHOLDER": "Valitse edustajan tyyppi", + "ERROR": "Edustajan tyyppi on pakollinen" }, "EMAIL": { "LABEL": "Sähköpostiosoite", @@ -90,7 +90,7 @@ } }, "SEARCH": { - "NO_RESULTS": "No results found." + "NO_RESULTS": "Ei hakutuloksia." } } } diff --git a/app/javascript/dashboard/i18n/locale/fi/campaign.json b/app/javascript/dashboard/i18n/locale/fi/campaign.json index 92df74be9..b20f634d7 100644 --- a/app/javascript/dashboard/i18n/locale/fi/campaign.json +++ b/app/javascript/dashboard/i18n/locale/fi/campaign.json @@ -1,56 +1,56 @@ { "CAMPAIGN": { - "HEADER": "Campaigns", - "SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on Add Campaign to create a new campaign. You can also edit or delete an existing campaign by clicking on the Edit or Delete button.", - "HEADER_BTN_TXT": "Create a campaign", + "HEADER": "Kampanja", + "SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on Add Campaign to create a new kampanja. You can also edit or delete an existing kampanja by clicking on the Edit or Delete button.", + "HEADER_BTN_TXT": "Luo kampanja", "ADD": { - "TITLE": "Create a campaign", + "TITLE": "Luo kampanja", "DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.", "CANCEL_BUTTON_TEXT": "Peruuta", "CREATE_BUTTON_TEXT": "Luo", "FORM": { "TITLE": { - "LABEL": "Title", - "PLACEHOLDER": "Please enter the title of campaign", - "ERROR": "Title is required" + "LABEL": "Otsikko", + "PLACEHOLDER": "Syötä kampanjan otsikko", + "ERROR": "Otsikko on pakollinen" }, "SCHEDULED_AT": { - "LABEL": "Scheduled time", - "PLACEHOLDER": "Please select the time", - "CONFIRM": "Confirm", - "ERROR": "Scheduled time is required" + "LABEL": "Kampanjan alkamisaika", + "PLACEHOLDER": "Syötä kampanja alkamisaika", + "CONFIRM": "Varmista", + "ERROR": "Alkamisaika on pakollinen" }, "AUDIENCE": { - "LABEL": "Audience", - "PLACEHOLDER": "Select the customer labels", - "ERROR": "Audience is required" + "LABEL": "Kohdeyleisö", + "PLACEHOLDER": "Valitse tunnisteet", + "ERROR": "Kohdeyleisö on pakollinen" }, "MESSAGE": { - "LABEL": "Message", - "PLACEHOLDER": "Please enter the message of campaign", - "ERROR": "Message is required" + "LABEL": "Viesti", + "PLACEHOLDER": "Syötä kampanjan viesti", + "ERROR": "Viesti on pakollinen" }, "SENT_BY": { "LABEL": "Lähettäjä", - "PLACEHOLDER": "Please select the the content of campaign", - "ERROR": "Sender is required" + "PLACEHOLDER": "Syötä kampanjan lähettäjä", + "ERROR": "Lähettäjä on pakollinen" }, "END_POINT": { "LABEL": "URL", - "PLACEHOLDER": "Please enter the URL", + "PLACEHOLDER": "Syötä kampanjan URL", "ERROR": "Anna kelvollinen URL-osoite" }, "TIME_ON_PAGE": { - "LABEL": "Time on page(Seconds)", - "PLACEHOLDER": "Please enter the time", - "ERROR": "Time on page is required" + "LABEL": "Aika sivulla (sekunttia)", + "PLACEHOLDER": "Syötä aika", + "ERROR": "Aika sivulla on pakollinen" }, - "ENABLED": "Enable campaign", - "SUBMIT": "Add Campaign" + "ENABLED": "Aktivoi kampanja", + "SUBMIT": "Lisää kampanja" }, "API": { - "SUCCESS_MESSAGE": "Campaign created successfully", - "ERROR_MESSAGE": "There was an error. Please try again." + "SUCCESS_MESSAGE": "Kampanja luotu onnistuneesti", + "ERROR_MESSAGE": "Hö! Kampanjaa luodessa tapahtui virhe" } }, "DELETE": { @@ -62,41 +62,41 @@ "NO": "Ei, säilytä " }, "API": { - "SUCCESS_MESSAGE": "Campaign deleted successfully", - "ERROR_MESSAGE": "Could not delete the campaign. Please try again later." + "SUCCESS_MESSAGE": "Kampanja poistettu onnistuneesti", + "ERROR_MESSAGE": "Hö! Kampanjaa poistaessa tapahtui virhe. Kokeile myöhemmin uudelleen" } }, "EDIT": { - "TITLE": "Edit campaign", + "TITLE": "Muokkaa kampanjaa", "UPDATE_BUTTON_TEXT": "Päivitä", "API": { - "SUCCESS_MESSAGE": "Campaign updated successfully", - "ERROR_MESSAGE": "Tapahtui virhe, yritä uudelleen" + "SUCCESS_MESSAGE": "Kampanjaa muokattu onnistuneesti", + "ERROR_MESSAGE": "Hö! Kampanjaa muokatessa tapahtui virhe" } }, "LIST": { - "LOADING_MESSAGE": "Loading campaigns...", - "404": "There are no campaigns created for this inbox.", + "LOADING_MESSAGE": "Ladataan kampanjoita...", + "404": "Tälle kansiolle ei ole luotu kampanjoita.", "TABLE_HEADER": { - "TITLE": "Title", - "MESSAGE": "Message", + "TITLE": "Otsikko", + "MESSAGE": "Viesti", "STATUS": "Tila", - "SENDER": "Sender", + "SENDER": "Lähettäjä", "URL": "URL", - "SCHEDULED_AT": "Scheduled time", - "TIME_ON_PAGE": "Time(Seconds)", - "CREATED_AT": "Created at" + "SCHEDULED_AT": "Aloitusaika", + "TIME_ON_PAGE": "Aika sivulla (sekunttia)", + "CREATED_AT": "Luotu" }, "BUTTONS": { - "ADD": "Add", + "ADD": "Luo uusi", "EDIT": "Muokkaa", "DELETE": "Poista" }, "STATUS": { "ENABLED": "Käytössä", "DISABLED": "Pois käytöstä", - "COMPLETED": "Completed", - "ACTIVE": "Active" + "COMPLETED": "Suoritettu", + "ACTIVE": "Käynnissä" }, "SENDER": { "BOT": "Botti" diff --git a/app/javascript/dashboard/i18n/locale/fi/chatlist.json b/app/javascript/dashboard/i18n/locale/fi/chatlist.json index 7529e32eb..0339d3111 100644 --- a/app/javascript/dashboard/i18n/locale/fi/chatlist.json +++ b/app/javascript/dashboard/i18n/locale/fi/chatlist.json @@ -81,6 +81,6 @@ "VIEW_TWEET_IN_TWITTER": "Näytä twiitti Twitterissä", "REPLY_TO_TWEET": "Vastaa tähän twiittiin", "NO_MESSAGES": "Ei Viestejä", - "NO_CONTENT": "No content available" + "NO_CONTENT": "Sisältöä ei saatavilla" } } diff --git a/app/javascript/dashboard/i18n/locale/fi/contact.json b/app/javascript/dashboard/i18n/locale/fi/contact.json index 9f4f0fcfb..8b16fc472 100644 --- a/app/javascript/dashboard/i18n/locale/fi/contact.json +++ b/app/javascript/dashboard/i18n/locale/fi/contact.json @@ -19,17 +19,17 @@ }, "LABELS": { "CONTACT": { - "TITLE": "Contact Labels", - "ERROR": "Couldn't update labels" + "TITLE": "Yhteystietotunnisteet", + "ERROR": "Tunnisteita ei voitu päivittää" }, "CONVERSATION": { "TITLE": "Keskustelutunnisteet", - "ADD_BUTTON": "Add Labels" + "ADD_BUTTON": "Lisää tunnisteita" }, "LABEL_SELECT": { - "TITLE": "Add Labels", - "PLACEHOLDER": "Search labels", - "NO_RESULT": "No labels found" + "TITLE": "Lisää tunnisteita", + "PLACEHOLDER": "Hae tunnisteita", + "NO_RESULT": "Tunnisteita ei löytynyt" } }, "MUTE_CONTACT": "Mykistä Keskustelu", @@ -71,8 +71,8 @@ "PHONE_NUMBER": { "PLACEHOLDER": "Anna yhteystiedon puhelinnumero", "LABEL": "Puhelinnumero", - "HELP": "Phone number should be of E.164 format eg: +1415555555 [+][country code][area code][local phone number]", - "ERROR": "Phone number should be either empty or of E.164 format" + "HELP": "Puhelinnumeron tulee olla E.164 formaattia, esim: +1415555555 [+][maakoodi][aluekoodi][puhelinnumero]", + "ERROR": "Puhelinnumeron tulee olla tyhjä tai E.164 formaattia" }, "LOCATION": { "PLACEHOLDER": "Anna yhteystiedon sijainti", @@ -109,129 +109,129 @@ "BUTTON_LABEL": "Aloita keskustelu", "TITLE": "Uusi keskustelu", "DESC": "Aloita uusi keskustelu lähettämällä uusi viesti.", - "NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.", + "NO_INBOX": "Kansiota jolla tälle kontaktille olisi voinut luoda keskustelun ei löytynyt.", "FORM": { "TO": { - "LABEL": "To" + "LABEL": "Kohde" }, "INBOX": { - "LABEL": "Inbox", - "ERROR": "Select an inbox" + "LABEL": "Kansio", + "ERROR": "Valitse kansio" }, "MESSAGE": { - "LABEL": "Message", - "PLACEHOLDER": "Write your message here", - "ERROR": "Message can't be empty" + "LABEL": "Viesti", + "PLACEHOLDER": "Kirjoita viestisi tähän", + "ERROR": "Viesti ei voi olla tyhjä" }, - "SUBMIT": "Send message", + "SUBMIT": "Lähetä viesti", "CANCEL": "Peruuta", - "SUCCESS_MESSAGE": "Message sent!", - "ERROR_MESSAGE": "Couldn't send! try again" + "SUCCESS_MESSAGE": "Viesti lähetetty!", + "ERROR_MESSAGE": "Hö! Virhe, yritä uudelleen" } }, "CONTACTS_PAGE": { "HEADER": "Yhteystiedot", - "FIELDS": "Contact fields", + "FIELDS": "Yhteystietokentät", "SEARCH_BUTTON": "Etsi", "SEARCH_INPUT_PLACEHOLDER": "Etsi yhteystietoja", "LIST": { "LOADING_MESSAGE": "Ladataan yhteystietoja...", "404": "Ei hakua vastaavia yhteystietoja 🔍", - "NO_CONTACTS": "There are no available contacts", + "NO_CONTACTS": "Yhtään kontaktia ei löytynyt.", "TABLE_HEADER": { "NAME": "Nimi", "PHONE_NUMBER": "Puhelinnumero", "CONVERSATIONS": "Keskustelut", - "LAST_ACTIVITY": "Last Activity", - "COUNTRY": "Country", - "CITY": "City", - "SOCIAL_PROFILES": "Social Profiles", + "LAST_ACTIVITY": "Viimeksi aktiivinen", + "COUNTRY": "Maa", + "CITY": "Kaupunki", + "SOCIAL_PROFILES": "Sosiaalisen median tilit", "COMPANY": "Yritys", "EMAIL_ADDRESS": "Sähköpostiosoite" }, - "VIEW_DETAILS": "View details" + "VIEW_DETAILS": "Katso lisätietoja" } }, "REMINDER": { "ADD_BUTTON": { - "BUTTON": "Add", - "TITLE": "Shift + Enter to create a task" + "BUTTON": "Lisää", + "TITLE": "Shift + Enter luodaksesi tehtävän" }, "FOOTER": { - "DUE_DATE": "Due date", - "LABEL_TITLE": "Set type" + "DUE_DATE": "Eräpäivä", + "LABEL_TITLE": "Aseta tyyppi" } }, "NOTES": { "HEADER": { - "TITLE": "Notes" + "TITLE": "Muistiinpanot" }, "ADD": { - "BUTTON": "Add", - "PLACEHOLDER": "Add a note", - "TITLE": "Shift + Enter to create a note" + "BUTTON": "Lisää", + "PLACEHOLDER": "Lisää muistiinpano", + "TITLE": "Shift + Enter luodaksesi muistiinpano" }, "FOOTER": { - "BUTTON": "View all notes" + "BUTTON": "Näe kaikki muistiinpanot" } }, "EVENTS": { "HEADER": { - "TITLE": "Activities" + "TITLE": "Aktiviteetit" }, "BUTTON": { - "PILL_BUTTON_NOTES": "notes", - "PILL_BUTTON_EVENTS": "events", + "PILL_BUTTON_NOTES": "muistiinpanot", + "PILL_BUTTON_EVENTS": "tapahtumat", "PILL_BUTTON_CONVO": "keskustelut" } }, "CUSTOM_ATTRIBUTES": { "TITLE": "Mukautetut attribuutit", - "BUTTON": "Add custom attribute", + "BUTTON": "Lisää kustomoitu attribuutti", "ADD": { - "TITLE": "Create custom attribute", - "DESC": "Add custom information to this contact." + "TITLE": "Lisää kustomoitu attribuutti", + "DESC": "Lisää lisätietoja tähän kontaktiin." }, "FORM": { - "CREATE": "Add attribute", + "CREATE": "Lisää atribuutti", "CANCEL": "Peruuta", "NAME": { - "LABEL": "Custom attribute name", - "PLACEHOLDER": "Eg: shopify id", - "ERROR": "Invalid custom attribute name" + "LABEL": "Attribuutin nimi", + "PLACEHOLDER": "Esim: shopify id", + "ERROR": "Virheellinen nimi" }, "VALUE": { - "LABEL": "Attribute value", - "PLACEHOLDER": "Eg: 11901 " + "LABEL": "Attribuutin arvo", + "PLACEHOLDER": "Esim: 11901 " } } }, "MERGE_CONTACTS": { - "TITLE": "Merge contacts", - "DESCRIPTION": "Merge contact is helpful when you have duplicated entries of the same contact. Merging action takes a primary contact and a child contact. After merging, all details in the primary contact will remain the same. If the primary contact doesn't have a field, then the value from the child contact will be used after merging. If a conflict happens, fields in primary contact will remain unaffected, but fields from secondary will be copied to the custom attributes in the primary contact.", + "TITLE": "Yhdistä kontakteja", + "DESCRIPTION": "Yhdistää kaksi kontakia. Ensimmäinen kontaktin tiedot pysyvät koskemattomina, mutta toisen attribuutit kopioidaan ensimmäiseen kustomoituina attribuutteina.", "PRIMARY": { - "TITLE": "Primary contact" + "TITLE": "Ensimmäinen kontakti" }, "CHILD": { - "TITLE": "Contact to merge", - "PLACEHOLDER": "Choose a contact" + "TITLE": "Yhdistettävä kontakti", + "PLACEHOLDER": "Valitse kontakti" }, "SUMMARY": { - "TITLE": "Summary", - "DELETE_WARNING": "Contact of %{childContactName}will be deleted.", - "ATTRIBUTE_WARNING": "Contact details of %{childContactName} will be copied to %{primaryContactName}." + "TITLE": "Yhteenveto", + "DELETE_WARNING": "Kontakti %{childContactName} poistetaan.", + "ATTRIBUTE_WARNING": "Kontaktin %{childContactName} tiedot kopioidaan kontaktiin %{primaryContactName}." }, "SEARCH": { "ERROR": "ERROR_MESSAGE" }, "FORM": { - "SUBMIT": " Merge contacts", + "SUBMIT": " Yhdistä", "CANCEL": "Peruuta", "CHILD_CONTACT": { - "ERROR": "Select a child contact to merge" + "ERROR": "Valitse yhdistettävä tili" }, - "SUCCESS_MESSAGE": "Contact merged successfully", - "ERROR_MESSAGE": "Could not merge contcts, try again!" + "SUCCESS_MESSAGE": "Kontaktien yhdistäminen onnistui", + "ERROR_MESSAGE": "Virhe, yritä uudelleen!" } } } diff --git a/app/javascript/dashboard/i18n/locale/fi/conversation.json b/app/javascript/dashboard/i18n/locale/fi/conversation.json index 74a060e29..9bfd8d2f5 100644 --- a/app/javascript/dashboard/i18n/locale/fi/conversation.json +++ b/app/javascript/dashboard/i18n/locale/fi/conversation.json @@ -27,11 +27,11 @@ "REMOVE_SELECTION": "Poista valinnat", "DOWNLOAD": "Lataa", "UPLOADING_ATTACHMENTS": "Ladataan liitteitä...", - "SUCCESS_DELETE_MESSAGE": "Message deleted successfully", - "FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again", - "NO_RESPONSE": "No response", - "RATING_TITLE": "Rating", - "FEEDBACK_TITLE": "Feedback", + "SUCCESS_DELETE_MESSAGE": "Viesti poistettu onnistuneesti", + "FAIL_DELETE_MESSSAGE": "Viestin poistaminen epäonnistui.", + "NO_RESPONSE": "Ei vastausta", + "RATING_TITLE": "Arvostelut", + "FEEDBACK_TITLE": "Palaute", "HEADER": { "RESOLVE_ACTION": "Ratkaise", "REOPEN_ACTION": "Uudelleenavaa", @@ -41,7 +41,7 @@ "DETAILS": "tiedot" }, "RESOLVE_DROPDOWN": { - "OPEN_BOT": "Open with bot" + "OPEN_BOT": "Avaa botilla" }, "FOOTER": { "MSG_INPUT": "Vaihto + enter siirtyäksesi uudelle riville. Aloita '/' valitaksesi tallennettu vastaus.", @@ -53,17 +53,17 @@ "SEND": "Lähetä", "CREATE": "Lisää muistiinpano", "TWEET": "Twiittaa", - "TIP_FORMAT_ICON": "Show rich text editor", - "TIP_EMOJI_ICON": "Show emoji selector", - "TIP_ATTACH_ICON": "Attach files", - "ENTER_TO_SEND": "Enter to send", - "DRAG_DROP": "Drag and drop here to attach" + "TIP_FORMAT_ICON": "Näytä rikas tekstieditori", + "TIP_EMOJI_ICON": "Näytä emojivalitsin", + "TIP_ATTACH_ICON": "Liitä tiedosto", + "ENTER_TO_SEND": "Paina Enter lähettääksesi", + "DRAG_DROP": "Pudota tiedosto tähän liittääksesi" }, "VISIBLE_TO_AGENTS": "Yksityinen huomautus: Näkyy vain sinulle ja tiimillesi", "CHANGE_STATUS": "Keskustelun tila muutettu", "CHANGE_AGENT": "Keskustelun vastaanottaja vaihdettu", - "CHANGE_TEAM": "Conversation team changed", - "FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit", + "CHANGE_TEAM": "Keskustelun tiimi vaihdettu", + "FILE_SIZE_LIMIT": "Tiedoston koko ylittää maksimin: {MAXIMUM_FILE_UPLOAD_SIZE} ", "SENT_BY": "Lähettäjä:", "ASSIGNMENT": { "SELECT_AGENT": "Valitse edustaja", @@ -93,35 +93,35 @@ } }, "ONBOARDING": { - "TITLE": "Hey 👋, Welcome to %{installationName}!", + "TITLE": "Hei 👋, tervetuloa %{installationName}!", "DESCRIPTION": "Thanks for signing up. We want you to get the most out of %{installationName}. Here are a few things you can do in %{installationName} to make the experience delightful.", "READ_LATEST_UPDATES": "Read our latest updates", "ALL_CONVERSATION": { - "TITLE": "All your conversations in one place", - "DESCRIPTION": "View all the conversations from your customers in one single dashboard. You can filter the conversations by the incoming channel, label and status." + "TITLE": "Kaikki keskustelut yhdessä paikassa", + "DESCRIPTION": "Kaikki asiakkaiden viestit yhdessä ohjauspaneelissa. Voit filtteröidä viestejä tunnisteen, kansion ja tiimin perusteella." }, "TEAM_MEMBERS": { - "TITLE": "Invite your team members", - "DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email address to the agent list.", - "NEW_LINK": "Click here to invite a team member" + "TITLE": "Kutsu tiimijäseniä", + "DESCRIPTION": "Koska olet valmis palvelemaan asiakkaita, kutsu muutama tiimijäsentä auttamaan sinua!", + "NEW_LINK": "Paina tästä kutsuaksesi tiimijäsen" }, "INBOXES": { - "TITLE": "Connect Inboxes", - "DESCRIPTION": "Connect various channels through which your customers would be talking to you. It can be a website live-chat, your Facebook or Twitter page or even your WhatsApp number.", - "NEW_LINK": "Click here to create an inbox" + "TITLE": "Yhdistä kansioita", + "DESCRIPTION": "Yhdistä monta eri viestikanavaa jolla asiakkaat voivat ottaa sinun yhteyden. Esimerkiksi widgetti omille verkkosivuille, Twitterissä tai Whatsapissa.", + "NEW_LINK": "Paina tästä luodaksesi kansio" }, "LABELS": { - "TITLE": "Organize conversations with labels", - "DESCRIPTION": "Labels provide an easier way to categorize your conversation. Create some labels like #support-enquiry, #billing-question etc., so that you can use them in a conversation later.", - "NEW_LINK": "Click here to create tags" + "TITLE": "Järjestä keskusteluja tunnisteilla", + "DESCRIPTION": "Tunnisteet auttavat sinua järjestelemään eri keskusteluja.", + "NEW_LINK": "Paina tästä luodaksesi uusi tunniste" } }, "CONVERSATION_SIDEBAR": { - "ASSIGNEE_LABEL": "Assigned Agent", - "SELF_ASSIGN": "Assign to me", - "TEAM_LABEL": "Assigned Team", + "ASSIGNEE_LABEL": "Määrätty edustaja", + "SELF_ASSIGN": "Määrätty minulle", + "TEAM_LABEL": "Määrätty tiimi", "SELECT": { - "PLACEHOLDER": "None" + "PLACEHOLDER": "Ei mikään" } } } diff --git a/app/javascript/dashboard/i18n/locale/fi/csatMgmt.json b/app/javascript/dashboard/i18n/locale/fi/csatMgmt.json index d7d2efc2a..545970056 100644 --- a/app/javascript/dashboard/i18n/locale/fi/csatMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fi/csatMgmt.json @@ -1,6 +1,6 @@ { "CSAT": { - "TITLE": "Rate your conversation", - "PLACEHOLDER": "Tell us more..." + "TITLE": "Arvostele keskustelu", + "PLACEHOLDER": "Kerro meille lisää" } } diff --git a/app/javascript/dashboard/i18n/locale/fi/generalSettings.json b/app/javascript/dashboard/i18n/locale/fi/generalSettings.json index b52fd6c8f..ee4ad33f0 100644 --- a/app/javascript/dashboard/i18n/locale/fi/generalSettings.json +++ b/app/javascript/dashboard/i18n/locale/fi/generalSettings.json @@ -43,7 +43,7 @@ "CUSTOM_EMAIL_DOMAIN_ENABLED": "Voit nyt vastaanottaa sähköposteja mukautetulla verkkotunnuksellasi." } }, - "UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance." + "UPDATE_CHATWOOT": "Uusi ChatWoot-päivitys on saatavilla. Uusin versio on %{latestChatwootVersion}" }, "FORMS": { "MULTISELECT": { @@ -54,10 +54,10 @@ }, "NOTIFICATIONS_PAGE": { "HEADER": "Ilmoitukset", - "MARK_ALL_DONE": "Mark All Done", + "MARK_ALL_DONE": "Merkkaa kaikki luetuksi", "LIST": { - "LOADING_MESSAGE": "Loading notifications...", - "404": "No Notifications", + "LOADING_MESSAGE": "Ladataan ilmoituksia...", + "404": "Ei ilmoituksia", "TABLE_HEADER": [ "Nimi", "Puhelinnumero", @@ -66,10 +66,10 @@ ] }, "TYPE_LABEL": { - "conversation_creation": "New conversation", - "conversation_assignment": "Conversation Assigned", - "assigned_conversation_new_message": "New Message", - "conversation_mention": "Mention" + "conversation_creation": "Uusi keskustelu", + "conversation_assignment": "Keskustelu määrätty", + "assigned_conversation_new_message": "Uusi viesti", + "conversation_mention": "Mainitse" } } } diff --git a/app/javascript/dashboard/i18n/locale/fi/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/fi/inboxMgmt.json index fdce45c5b..ed2364f5e 100644 --- a/app/javascript/dashboard/i18n/locale/fi/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fi/inboxMgmt.json @@ -1,7 +1,7 @@ { "INBOX_MGMT": { "HEADER": "Saapuneet-kansiot", - "SIDEBAR_TXT": "

    Postilaatikko

    Kun yhdistät sivuston tai facebook-sivun Chatwotiin, sitä kutsutaan postilaatikoksi. Sinulla voi olla rajoittamaton määrä postilaatikoita Chatwoot tililläsi.

    Klikkaa Lisää postilaatikko yhdistääksesi verkkosivuston tai Facebook-sivun.

    Kojelaudalla näet kaikki keskustelut kaikista saapuneet-kansiostasi yhdessä paikassa ja vastaat niihin `Keskustelut`-välilehdessä.

    Voit myös nähdä postilaatikkoon liittyviä keskusteluja klikkaamalla postilaatikon nimeä kojelaudan vasemmassa paneelissa.

    ", + "SIDEBAR_TXT": "

    Postilaatikko

    Kun yhdistät sivuston tai Facebook-sivun Chatwotiin, sitä kutsutaan postilaatikoksi. Sinulla voi olla rajoittamaton määrä postilaatikoita Chatwoot tililläsi.

    Klikkaa Lisää postilaatikko yhdistääksesi verkkosivuston tai Facebook-sivun.

    Kojelaudalla näet kaikki keskustelut kaikista saapuneet-kansiostasi yhdessä paikassa ja vastaat niihin `Keskustelut`-välilehdessä.

    Voit myös nähdä postilaatikkoon liittyviä keskusteluja klikkaamalla postilaatikon nimeä kojelaudan vasemmassa paneelissa.

    ", "LIST": { "404": "Tähän tiliin ei ole liitetty saapuneet-kansiota." }, @@ -30,7 +30,7 @@ "ADD": { "CHANNEL_NAME": { "LABEL": "Kansion nimi", - "PLACEHOLDER": "Enter your inbox name (eg: Acme Inc)" + "PLACEHOLDER": "Valitse kansion nimi (esim: Acme Oy)" }, "WEBSITE_NAME": { "LABEL": "Sivuston nimi", @@ -47,7 +47,7 @@ }, "TWITTER": { "HELP": "Lisätäksesi twitter-profiilin kanavaksesi, sinun tulee autentikoida twitter-tilisi klikkaamalla \"Kirjaudu sisään Twitterillä\" ", - "ERROR_MESSAGE": "There was an error connecting to Twitter, please try again" + "ERROR_MESSAGE": "Twitteriin yhdistäessä tapahtui virhe" }, "WEBSITE_CHANNEL": { "TITLE": "Sivuston chat", @@ -83,7 +83,7 @@ "IN_A_FEW_MINUTES": "Muutamassa minuutissa", "IN_A_FEW_HOURS": "Muutamassa tunnissa", "IN_A_DAY": "Päivän kuluessa", - "HELP_TEXT": "Vastausaika näytetään chat -widgetissä" + "HELP_TEXT": "Vastausaika näytetään chat-widgetissä" }, "WIDGET_COLOR": { "LABEL": "Widgetin väri", @@ -128,12 +128,12 @@ } }, "SMS": { - "TITLE": "SMS Channel via Twilio", - "DESC": "Start supporting your customers via SMS with Twilio integration." + "TITLE": "SMS Twilio:n kautta", + "DESC": "Ala palvelemaan asiakkaitasi tekstiviestien avulla käyttäen Twilio-alustaa" }, "WHATSAPP": { - "TITLE": "Whatsapp Channel via Twilio", - "DESC": "Start supporting your customers via Whatsapp with Twilio integration." + "TITLE": "Whatsapp Twilio:n kautta", + "DESC": "Ala palvelemaan asiakkaitasi Whatsapp-viestien avulla käyttäen Twilio-alustaa" }, "API_CHANNEL": { "TITLE": "API-rajapinta", @@ -173,8 +173,8 @@ "FINISH_MESSAGE": "Aloita välittämällä sähköpostit seuraavaan osoitteeseen." }, "AUTH": { - "TITLE": "Choose a channel", - "DESC": "Chatwoot supports live-chat widget, Facebook page, Twitter profile, Whatsapp, Email etc., as channels. If you want to build a custom channel, you can create it using the API channel. Select one channel from the options below to proceed." + "TITLE": "Valitse kanava", + "DESC": "Chatwoot tukee live-chat widgettiä, Facebook-sivua, Twitter-profiilia, Whatsappia, Sähköpostia jne., as channels. Voit luoda oman kanavan API-rajapinnalla." }, "AGENTS": { "TITLE": "Edustajat", @@ -264,16 +264,16 @@ "INBOX_AGENTS": "Edustajat", "INBOX_AGENTS_SUB_TEXT": "Lisää tai poista edustajia tästä saapuneet-kansiosta", "UPDATE": "Päivitä", - "ENABLE_EMAIL_COLLECT_BOX": "Enable email collect box", - "ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation", + "ENABLE_EMAIL_COLLECT_BOX": "Aktivoi sähköpostilaatikko", + "ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Aktivoi tai disabloi sähköpostilaatikko uusissa keskusteluissa", "AUTO_ASSIGNMENT": "Ota automaattinen delegointi käyttöön", - "ENABLE_CSAT": "Enable CSAT", - "ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation", + "ENABLE_CSAT": "Aktivoi ASTK", + "ENABLE_CSAT_SUB_TEXT": "Aktivoi/Disabloi ASTK(ASiakasTyytyväisyysKysely) keskustelun ratkaisun jälkeen", "INBOX_UPDATE_TITLE": "Postilaatikon tiedot", "INBOX_UPDATE_SUB_TEXT": "Päivitä postilaatikon asetukset", "AUTO_ASSIGNMENT_SUB_TEXT": "Ota käyttöön tai poista käytöstä automaattinen keskusteluiden delegointi edustajille.", - "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_VERIFICATION": "Käyttäjän identiteettivarmistus", + "HMAC_DESCRIPTION": "Varmistaaksemme käyttäjän identiteetin, SDK sallii sinun antaa `identifier_hash` jokaiselle käyttäjälle. Voit generoida HMAC käyttäen 'sha256' avaimella joka on alla." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Uudelleenvaltuuta", @@ -282,38 +282,38 @@ "MESSAGE_ERROR": "Tapahtui virhe, yritä uudelleen" }, "PRE_CHAT_FORM": { - "DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.", + "DESCRIPTION": "Pikakysely kysyy käyttäjän sähköpostin ja nimen ennen keskustelua", "ENABLE": { - "LABEL": "Enable pre chat form", + "LABEL": "Aktivoi pikakysely", "OPTIONS": { - "ENABLED": "Yes", - "DISABLED": "No" + "ENABLED": "Kyllä", + "DISABLED": "Ei" } }, "PRE_CHAT_MESSAGE": { - "LABEL": "Pre Chat Message", - "PLACEHOLDER": "This message would be visible to the users along with the form" + "LABEL": "Pikakyselyviesti", + "PLACEHOLDER": "Tämä viesti näytetään käyttäjille joille pikakysely näytetään" }, "REQUIRE_EMAIL": { - "LABEL": "Visitors should provide their name and email address before starting the chat" + "LABEL": "Käyttäjien tulee antaa sähköpostiosoite ja nimi ennen keskustelun aloitusta" } }, "BUSINESS_HOURS": { - "TITLE": "Set your availability", - "SUBTITLE": "Set your availability on your livechat widget", - "WEEKLY_TITLE": "Set your weekly hours", - "TIMEZONE_LABEL": "Select timezone", - "UPDATE": "Update business hours settings", - "TOGGLE_AVAILABILITY": "Enable business availability for this inbox", - "UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors", - "UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.", - "TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.", + "TITLE": "Aseta tilasi", + "SUBTITLE": "Aseta tilasi livechat widgetissä", + "WEEKLY_TITLE": "Aseta viikkotyöaikasi", + "TIMEZONE_LABEL": "Aseta aikavyöhyke", + "UPDATE": "Aseta työaika", + "TOGGLE_AVAILABILITY": "Aktivoi työaikasaatavuus tälle kansiolle", + "UNAVAILABLE_MESSAGE_LABEL": "Ei saatavilla-viesti käyttäjille", + "UNAVAILABLE_MESSAGE_DEFAULT": "Emme ole saatavilla juuri nyt. Jätä viesti ja vastaamme heti kun pystymme.", + "TOGGLE_HELP": "Aktivoimalla työaikasaatavuus livechat widgetissä näytetään olevanne saatavilla, vaikka kukaan edustaja ei olisi paikalla. Näiden aikojen ulkopuolella käyttäjille näytetään varoitus ja pikakysely.", "DAY": { - "ENABLE": "Enable availability for this day", - "UNAVAILABLE": "Unavailable", - "HOURS": "hours", - "VALIDATION_ERROR": "Starting time should be before closing time.", - "CHOOSE": "Choose" + "ENABLE": "Aktivoi saatavuus tälle päivälle", + "UNAVAILABLE": "Ei saatavilla", + "HOURS": "tuntia", + "VALIDATION_ERROR": "Avaamisajan tulisi olla ennen sulkemisaikaa.", + "CHOOSE": "Valitse" } } } diff --git a/app/javascript/dashboard/i18n/locale/fi/integrationApps.json b/app/javascript/dashboard/i18n/locale/fi/integrationApps.json index ac9f76d68..3bdbf7a6a 100644 --- a/app/javascript/dashboard/i18n/locale/fi/integrationApps.json +++ b/app/javascript/dashboard/i18n/locale/fi/integrationApps.json @@ -1,36 +1,36 @@ { "INTEGRATION_APPS": { - "FETCHING": "Fetching Integrations", - "NO_HOOK_CONFIGURED": "There are no %{integrationId} integrations configured in this account.", - "HEADER": "Applications", + "FETCHING": "Haetaan integraatioita", + "NO_HOOK_CONFIGURED": "Integraatiota IDllä %{integrationId} ei löytynyt tai ei ole konfiguroitu.", + "HEADER": "Applikaatiot", "STATUS": { "ENABLED": "Käytössä", "DISABLED": "Pois käytöstä" }, "CONFIGURE": "Määrittele", - "ADD_BUTTON": "Add a new hook", + "ADD_BUTTON": "Lisää uusi webhook", "DELETE": { "TITLE": { - "INBOX": "Confirm deletion", - "ACCOUNT": "Disconnect" + "INBOX": "Varmista poisto", + "ACCOUNT": "Katkaise" }, "MESSAGE": { "INBOX": "Oletko varma että haluat poistaa?", - "ACCOUNT": "Are you sure to disconnect?" + "ACCOUNT": "Oletko varma että haluat katkaista?" }, "CONFIRM_BUTTON_TEXT": { "INBOX": "Kyllä, poista", - "ACCOUNT": "Yes, Disconnect" + "ACCOUNT": "Kyllä, katkaise" }, "CANCEL_BUTTON_TEXT": "Peruuta", "API": { - "SUCCESS_MESSAGE": "Hook deleted successfully", + "SUCCESS_MESSAGE": "Webhook poistettu onnistuneesti", "ERROR_MESSAGE": "Yhteyden muodostaminen Woot-palvelimelle ei onnistunut, yritä myöhemmin uudelleen" } }, "LIST": { - "FETCHING": "Fetching integration hooks", - "INBOX": "Inbox", + "FETCHING": "Haetaan integraatiowebhookkeja", + "INBOX": "Kansio", "DELETE": { "BUTTON_TEXT": "Poista" } @@ -38,14 +38,14 @@ "ADD": { "FORM": { "INBOX": { - "LABEL": "Select Inbox", - "PLACEHOLDER": "Select Inbox" + "LABEL": "Valitse kansio", + "PLACEHOLDER": "Valitse kansio" }, "SUBMIT": "Luo", "CANCEL": "Peruuta" }, "API": { - "SUCCESS_MESSAGE": "Integration hook added successfully", + "SUCCESS_MESSAGE": "Integraatio webhook lisätty", "ERROR_MESSAGE": "Yhteyden muodostaminen Woot-palvelimelle ei onnistunut, yritä myöhemmin uudelleen" } }, @@ -53,7 +53,7 @@ "BUTTON_TEXT": "Yhdistä" }, "DISCONNECT": { - "BUTTON_TEXT": "Disconnect" + "BUTTON_TEXT": "Katkaise" }, "SIDEBAR_DESCRIPTION": { "DIALOGFLOW": "Dialogflow is a natural language understanding platform that makes it easy to design and integrate a conversational user interface into your mobile app, web application, device, bot, interactive voice response system, and so on.

    Dialogflow integration with %{installationName} allows you to configure a Dialogflow bot with your inboxes which lets the bot handle the queries initially and hand them over to an agent when needed. Dialogflow can be used to qualifying the leads, reduce the workload of agents by providing frequently asked questions etc.

    To add Dialogflow, you need to create a Service Account in your Google project console and share the credentials. Please refer to the Dialogflow docs for more information." diff --git a/app/javascript/dashboard/i18n/locale/fi/login.json b/app/javascript/dashboard/i18n/locale/fi/login.json index 8cf16346f..2210efddd 100644 --- a/app/javascript/dashboard/i18n/locale/fi/login.json +++ b/app/javascript/dashboard/i18n/locale/fi/login.json @@ -10,7 +10,7 @@ "PLACEHOLDER": "Salasana" }, "API": { - "SUCCESS_MESSAGE": "Kirjautuminen Onnistui", + "SUCCESS_MESSAGE": "Kirjautuminen onnistui", "ERROR_MESSAGE": "Yhteyden muodostaminen Woot-palvelimelle ei onnistunut, yritä myöhemmin uudelleen", "UNAUTH": "Käyttäjätunnus / salasana virheellinen. Yritä uudelleen" }, diff --git a/app/javascript/dashboard/i18n/locale/fi/report.json b/app/javascript/dashboard/i18n/locale/fi/report.json index ee1930e89..0f22be8c8 100644 --- a/app/javascript/dashboard/i18n/locale/fi/report.json +++ b/app/javascript/dashboard/i18n/locale/fi/report.json @@ -1,6 +1,6 @@ { "REPORT": { - "HEADER": "Overview", + "HEADER": "Yleiskatsaus", "LOADING_CHART": "Ladataan kaaviotietoja...", "NO_ENOUGH_DATA": "Emme ole saaneet tarpeeksi dataa raportin luomiseen, yritä myöhemmin uudelleen.", "DOWNLOAD_AGENT_REPORTS": "Lataa edustajaraportit", @@ -41,24 +41,24 @@ }, { "id": 2, - "name": "Last 3 months" + "name": "Viimeiset 3 kuukautta" }, { "id": 3, - "name": "Last 6 months" + "name": "Viimeiset 6 kuukautta" }, { "id": 4, - "name": "Last year" + "name": "Viimeinen vuosi" }, { "id": 5, - "name": "Custom date range" + "name": "Oma aikajakso" } ], "CUSTOM_DATE_RANGE": { - "CONFIRM": "Apply", - "PLACEHOLDER": "Select date range" + "CONFIRM": "Valitse", + "PLACEHOLDER": "Valitse aikajakso" } }, "CSAT_REPORTS": { diff --git a/app/javascript/dashboard/i18n/locale/fi/settings.json b/app/javascript/dashboard/i18n/locale/fi/settings.json index 3f731b29e..fc81707ea 100644 --- a/app/javascript/dashboard/i18n/locale/fi/settings.json +++ b/app/javascript/dashboard/i18n/locale/fi/settings.json @@ -19,18 +19,18 @@ "PASSWORD_SECTION": { "TITLE": "Salasana", "NOTE": "Salasanan vaihtaminen kirjaa sinut ulos muilta laitteilta.", - "BTN_TEXT": "Change password" + "BTN_TEXT": "Vaihda salasana" }, "ACCESS_TOKEN": { "TITLE": "Access Token", "NOTE": "Tätä tunnusta voidaan käyttää, jos olet rakentamassa API-pohjaista integraatiota" }, "AUDIO_NOTIFICATIONS_SECTION": { - "TITLE": "Audio Notifications", - "NOTE": "Enable audio notifications in dashboard for new messages and conversations.", - "NONE": "None", - "ASSIGNED": "Assigned Conversations", - "ALL_CONVERSATIONS": "All Conversations" + "TITLE": "Ääni-ilmoitukset", + "NOTE": "Aktivoi ääni-ilmoitukset hallintapaneelissa. Soittaa äänimerkin uuden viestin tullessa.", + "NONE": "Ei missään", + "ASSIGNED": "Määrätyt keskustelut", + "ALL_CONVERSATIONS": "Kaikki keskustelut" }, "EMAIL_NOTIFICATIONS_SECTION": { "TITLE": "Sähköposti-ilmoitukset", @@ -81,9 +81,9 @@ "PLACEHOLDER": "Sähköposti, joka näytetään keskusteluissa" }, "CURRENT_PASSWORD": { - "LABEL": "Current password", - "ERROR": "Please enter the current password", - "PLACEHOLDER": "Please enter the current password" + "LABEL": "Nykyinen salasana", + "ERROR": "Nykyinen salasana on pakollinen", + "PLACEHOLDER": "Syötä nykyinen salasana" }, "PASSWORD": { "LABEL": "Salasana", @@ -139,13 +139,13 @@ "CANNED_RESPONSES": "Tallennetut vastaukset", "INTEGRATIONS": "Integraatiot", "ACCOUNT_SETTINGS": "Tilin asetukset", - "APPLICATIONS": "Applications", + "APPLICATIONS": "Applikaatiot", "LABELS": "Tunnisteet", - "TEAMS": "Teams", - "ALL_CONTACTS": "All Contacts", - "TAGGED_WITH": "Tagged with", - "REPORTS_OVERVIEW": "Overview", - "CSAT": "CSAT" + "TEAMS": "Tiimit", + "ALL_CONTACTS": "Kaikki kontaktit", + "TAGGED_WITH": "Merkattu ", + "REPORTS_OVERVIEW": "Yleiskatsaus", + "CSAT": "ASTK" }, "CREATE_ACCOUNT": { "NEW_ACCOUNT": "Uusi tili", diff --git a/app/javascript/dashboard/i18n/locale/fi/teamsSettings.json b/app/javascript/dashboard/i18n/locale/fi/teamsSettings.json index 21565fe3e..5c05a47ae 100644 --- a/app/javascript/dashboard/i18n/locale/fi/teamsSettings.json +++ b/app/javascript/dashboard/i18n/locale/fi/teamsSettings.json @@ -1,125 +1,125 @@ { "TEAMS_SETTINGS": { - "NEW_TEAM": "Create new team", - "HEADER": "Teams", - "SIDEBAR_TXT": "

    Teams

    Teams let you organize your agents into groups based on their responsibilities.
    A user can be part of multiple teams. You can assign conversations to a team when you are working collaboratively.

    ", + "NEW_TEAM": "Luo uusi tiimi", + "HEADER": "Tiimi", + "SIDEBAR_TXT": "

    Tiimit

    Tiimit antavat tavan jakaa edustajat eri osa-alueisiin.
    Yksi edustaja voi olla monessa ryhmässä. Keskustelun voi määrätä ryhmälle.

    ", "LIST": { - "404": "There are no teams created on this account.", - "EDIT_TEAM": "Edit team" + "404": "Tällä tilillä ei ole tiimejä.", + "EDIT_TEAM": "Muokkaa tiimi'" }, "CREATE_FLOW": { "CREATE": { - "TITLE": "Create a new team", - "DESC": "Add a title and description to your new team." + "TITLE": "Luo uusi tiimi", + "DESC": "Lisää otsikko ja kuvaus tiimille." }, "AGENTS": { - "BUTTON_TEXT": "Add agents to team", - "TITLE": "Add agents to team - %{teamName}", - "DESC": "Add Agents to your newly created team. This lets you collaborate as a team on conversations, get notified on new events in the same conversation." + "BUTTON_TEXT": "Lisää edustajia tiimiin", + "TITLE": "Lisää edustajia tiimille - %{tiimiName}", + "DESC": "Lisää edustajia juuri luotuun tiimiin. Kaikki edustajat saavat ilmoitukset mikäli tiimi määrätään keskusteluun." }, "WIZARD": [ { "title": "Luo", - "route": "settings_teams_new", - "body": "Create a new team of agents." + "route": "settings_tiimis_new", + "body": "Luo uusi tiimi." }, { "title": "Lisää edustaja", - "route": "settings_teams_add_agents", - "body": "Add agents to the team." + "route": "settings_tiimis_add_agents", + "body": "Lisää edustajia tiimiin." }, { - "title": "Finish", - "route": "settings_teams_finish", + "title": "Valmista", + "route": "settings_tiimis_finish", "body": "Kaikki valmiina!" } ] }, "EDIT_FLOW": { "CREATE": { - "TITLE": "Edit your team details", - "DESC": "Edit title and description to your team.", - "BUTTON_TEXT": "Update team" + "TITLE": "Edit your tiimi details", + "DESC": "Edit title and description to your tiimi.", + "BUTTON_TEXT": "Update tiimi" }, "AGENTS": { - "BUTTON_TEXT": "Update agents in team", - "TITLE": "Add agents to team - %{teamName}", - "DESC": "Add Agents to your newly created team. All the added agents will be notified when a conversation is assigned to this team." + "BUTTON_TEXT": "Päivitä edustajat", + "TITLE": "Lisää agentteja tiimiin - %{tiimiName}", + "DESC": "Lisää edustajia juuri luotuun tiimiin. Kaikki edustajat saavat ilmoitukset mikäli tiimi määrätään keskusteluun." }, "WIZARD": [ { - "title": "Team details", - "route": "settings_teams_edit", - "body": "Change name, description and other details." + "title": "Tiimin tiedot", + "route": "settings_tiimis_edit", + "body": "Muuta nimeä, kuvausta ja muuta." }, { - "title": "Edit Agents", - "route": "settings_teams_edit_members", - "body": "Edit agents in your team." + "title": "Muuta edustajia", + "route": "settings_tiimis_edit_members", + "body": "Muuta tiimin edustajia." }, { - "title": "Finish", - "route": "settings_teams_edit_finish", + "title": "Valmista", + "route": "settings_tiimis_edit_finish", "body": "Kaikki valmiina!" } ] }, "TEAM_FORM": { - "ERROR_MESSAGE": "Couldn't save the team details. Try again." + "ERROR_MESSAGE": "Tiimin tallentaminen epäonnistui. Yritä uudelleen." }, "AGENTS": { "AGENT": "AGENT", "EMAIL": "Sähköposti", "BUTTON_TEXT": "Lisää edustaja", - "ADD_AGENTS": "Adding Agents to your Team...", - "SELECT": "select", - "SELECT_ALL": "select all agents", - "SELECTED_COUNT": "%{selected} out of %{total} agents selected." + "ADD_AGENTS": "Lisätään edustajia tiimiin...", + "SELECT": "valitse", + "SELECT_ALL": "valitse kaikki edustajat", + "SELECTED_COUNT": "%{selected}/%{total} edustajaa valittu." }, "ADD": { - "TITLE": "Add agents to team - %{teamName}", - "DESC": "Add Agents to your newly created team. This lets you collaborate as a team on conversations, get notified on new events in the same conversation.", - "SELECT": "select", - "SELECT_ALL": "select all agents", - "SELECTED_COUNT": "%{selected} out of %{total} agents selected.", + "TITLE": "Lisää edustaja tiimiin - %{tiimiName}", + "DESC": "Lisää edustajia juuri luotuun tiimiin. Kaikki edustajat saavat ilmoitukset mikäli tiimi määrätään keskusteluun.", + "SELECT": "valitse", + "SELECT_ALL": "valitse kaikki edustajat", + "SELECTED_COUNT": "%{selected}/%{total} edustajaa valittu.", "BUTTON_TEXT": "Lisää edustaja", - "AGENT_VALIDATION_ERROR": "Select atleaset one agent." + "AGENT_VALIDATION_ERROR": "Valitse vähintään yksi edustaja." }, "FINISH": { - "TITLE": "Your team is ready!", - "MESSAGE": "You can now collaborate as a team on conversations. Happy supporting ", - "BUTTON_TEXT": "Finish" + "TITLE": "Tiimi on valmis!", + "MESSAGE": "Voitte nyt työskennellä yhdessä keskusteluissa. ", + "BUTTON_TEXT": "Valmis" }, "DELETE": { "BUTTON_TEXT": "Poista", "API": { - "SUCCESS_MESSAGE": "Team deleted successfully.", - "ERROR_MESSAGE": "Couldn't delete the team. Try again." + "SUCCESS_MESSAGE": "Tiimi poistettu onnistuneesti.", + "ERROR_MESSAGE": "Hö! Virhe tiimiä poistaessa." }, "CONFIRM": { - "TITLE": "Are you sure want to delete - %{teamName}", - "PLACE_HOLDER": "Please type {teamName} to confirm", - "MESSAGE": "Deleting the team will remove the team assignment from the conversations assigned to this team.", + "TITLE": "Haluatko varmasti poistaa - %{tiimiName}", + "PLACE_HOLDER": "Kirjoita {tiimiName} varmistaaksesi", + "MESSAGE": "Tiimin poistaminen poistaa tiimin määrätyistä keskusteluista", "YES": "Poista ", "NO": "Peruuta" } }, "SETTINGS": "Asetukset", "FORM": { - "UPDATE": "Update team", - "CREATE": "Create team", + "UPDATE": "Päivitä tiimi", + "CREATE": "Luo tiimi", "NAME": { - "LABEL": "Team name", - "PLACEHOLDER": "Example: Sales, Customer Support" + "LABEL": "Tiimin nimi", + "PLACEHOLDER": "Esim: Myynti, Tuki" }, "DESCRIPTION": { - "LABEL": "Team Description", - "PLACEHOLDER": "Short description about this team." + "LABEL": "Tiimin kuvaus", + "PLACEHOLDER": "Lyhyt kuvaus tiimist'." }, "AUTO_ASSIGN": { - "LABEL": "Allow auto assign for this team." + "LABEL": "Salli automaattinen määräys tiimiin." }, - "SUBMIT_CREATE": "Create team" + "SUBMIT_CREATE": "Luo tiimi" } } } diff --git a/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json index 73c888d5d..8474d1855 100644 --- a/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fr/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Mettre à jour les paramètres de votre boîte de réception", "AUTO_ASSIGNMENT_SUB_TEXT": "Activer ou désactiver l'affectation automatique de nouvelles conversations aux agents ajoutés à cette boîte de réception.", "HMAC_VERIFICATION": "Validation de l'identité de l'utilisateur", - "HMAC_DESCRIPTION": "Afin de valider l'identité des utilisateurs, le SDK vous permet de passer un `identity_hash` pour chaque utilisateur. Vous pouvez générer HMAC en utilisant 'sha256' avec la clé indiquée ici." + "HMAC_DESCRIPTION": "Afin de valider l'identité des utilisateurs, le SDK vous permet de passer un `identifier_hash` pour chaque utilisateur. Vous pouvez générer HMAC en utilisant 'sha256' avec la clé indiquée ici." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Réautoriser", diff --git a/app/javascript/dashboard/i18n/locale/he/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/he/inboxMgmt.json index e962f1c9c..6b3cf0449 100644 --- a/app/javascript/dashboard/i18n/locale/he/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/he/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reauthorize", diff --git a/app/javascript/dashboard/i18n/locale/hi/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/hi/inboxMgmt.json index 7bb14a833..5044b9d1d 100644 --- a/app/javascript/dashboard/i18n/locale/hi/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/hi/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reauthorize", diff --git a/app/javascript/dashboard/i18n/locale/hu/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/hu/inboxMgmt.json index ce82a6607..d45534333 100644 --- a/app/javascript/dashboard/i18n/locale/hu/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/hu/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Frissítsd az inbox beállításaidat", "AUTO_ASSIGNMENT_SUB_TEXT": "Bekapcsolása vagy kikapcsolása az inboxhoz kapcsolódó automatikus ügynökhozzárendelésnek új beszélgetések esetén.", "HMAC_VERIFICATION": "Felhasználói fiók validálás", - "HMAC_DESCRIPTION": "A felhasználói fiók ellenőrzése céljából az SDK lehetőséget ad egy 'identity_hash' kódra minden felhasználónak. Ezt HMAC-al tudod generálni 'sha256'-t használva, az itt látható kóddal." + "HMAC_DESCRIPTION": "A felhasználói fiók ellenőrzése céljából az SDK lehetőséget ad egy 'identifier_hash' kódra minden felhasználónak. Ezt HMAC-al tudod generálni 'sha256'-t használva, az itt látható kóddal." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Újraengedélyezés", diff --git a/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json index 12b557016..90c155703 100644 --- a/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Perbarui pengaturan kotak masuk Anda", "AUTO_ASSIGNMENT_SUB_TEXT": "Mengaktifkan atau menonaktifkan penugasan otomatis percakapan baru ke agen yang ditambahkan ke kotak masuk ini.", "HMAC_VERIFICATION": "Validasi Identitas Pengguna", - "HMAC_DESCRIPTION": "Untuk memvalidasi identitas pengguna secara inorder, SDK memungkinkan Anda meneruskan `identity_hash` untuk setiap pengguna. Anda dapat membuat HMAC menggunakan 'sha256' dengan key yang ditampilkan di sini." + "HMAC_DESCRIPTION": "Untuk memvalidasi identitas pengguna secara inorder, SDK memungkinkan Anda meneruskan `identifier_hash` untuk setiap pengguna. Anda dapat membuat HMAC menggunakan 'sha256' dengan key yang ditampilkan di sini." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Otorisasi ulang", diff --git a/app/javascript/dashboard/i18n/locale/it/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/it/inboxMgmt.json index dada13d1c..063817c94 100644 --- a/app/javascript/dashboard/i18n/locale/it/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/it/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Aggiorna le impostazioni della posta in arrivo", "AUTO_ASSIGNMENT_SUB_TEXT": "Abilita o disabilita l'assegnazione automatica di nuove conversazioni agli agenti aggiunti a questa casella di posta.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Riautorizza", diff --git a/app/javascript/dashboard/i18n/locale/ja/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ja/inboxMgmt.json index 13e568a7c..b09773cfd 100644 --- a/app/javascript/dashboard/i18n/locale/ja/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ja/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "受信トレイの設定を更新する", "AUTO_ASSIGNMENT_SUB_TEXT": "この受信トレイに追加された担当者への新しい会話の自動割り当てを有効または無効にします。", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "再認証", diff --git a/app/javascript/dashboard/i18n/locale/ko/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ko/inboxMgmt.json index f382091aa..10dac1f43 100644 --- a/app/javascript/dashboard/i18n/locale/ko/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ko/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "받은 메시지함 설정 업데이트", "AUTO_ASSIGNMENT_SUB_TEXT": "받은 메시지에 추가된 에이전트에 새 대화를 자동으로 할당하거나 할당하지 않도록 설정하십시오.", "HMAC_VERIFICATION": "사용자 신원 검증", - "HMAC_DESCRIPTION": "사용자 신원 검증을 위해 SDK에서 각 사용자별 `identity_hash`를 전달할 수 있습니다. 옆에 보이는 키를 'sha256'으로 해싱하여 HMAC를 생성할 수 있습니다." + "HMAC_DESCRIPTION": "사용자 신원 검증을 위해 SDK에서 각 사용자별 `identifier_hash`를 전달할 수 있습니다. 옆에 보이는 키를 'sha256'으로 해싱하여 HMAC를 생성할 수 있습니다." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "재승인", diff --git a/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json index 82aa51356..919313089 100644 --- a/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ml/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "പുതിയ സംഭാഷണങ്ങളിൽ ലഭ്യമായ ഏജന്റുമാരുടെ ഓട്ടോമാറ്റിക് അസൈൻമെന്റ് പ്രാപ്തമാക്കുകയോ അപ്രാപ്തമാക്കുകയോ ചെയ്യുക", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "വീണ്ടും അംഗീകാരം നൽകുക", diff --git a/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json index 7bb14a833..5044b9d1d 100644 --- a/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reauthorize", diff --git a/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json index ac5f15108..e15d18ad4 100644 --- a/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/nl/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update uw inbox instellingen", "AUTO_ASSIGNMENT_SUB_TEXT": "In- of uitschakelen van de automatische toewijzing van nieuwe gesprekken aan de agenten die aan deze inbox zijn toegevoegd.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Autoriseer", diff --git a/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json index ca35636e1..ba32970a5 100644 --- a/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Oppdater innboksinnstillinger", "AUTO_ASSIGNMENT_SUB_TEXT": "Aktiver eller deaktiver automatisk tildeling av nye samtaler til agenter som er lagt til i denne innboksen.", "HMAC_VERIFICATION": "Brukeridentitetsvalidering", - "HMAC_DESCRIPTION": "For å bekrefte brukerens identitet vil SDK-en la deg legge inn en `identity_hash` for hver bruker. Du kan generere HMAC ved å benytte 'sha256' med nøkkelen vist her." + "HMAC_DESCRIPTION": "For å bekrefte brukerens identitet vil SDK-en la deg legge inn en `identifier_hash` for hver bruker. Du kan generere HMAC ved å benytte 'sha256' med nøkkelen vist her." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reautoriser", diff --git a/app/javascript/dashboard/i18n/locale/pl/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/pl/inboxMgmt.json index 5ac39610b..37ded8cb1 100644 --- a/app/javascript/dashboard/i18n/locale/pl/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pl/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Zaktualizuj ustawienia skrzynki odbiorczej", "AUTO_ASSIGNMENT_SUB_TEXT": "Włącz lub wyłącz automatyczne przypisywanie nowych rozmów do agentów dodanych do tej skrzynki odbiorczej.", "HMAC_VERIFICATION": "Weryfikacja tożsamości użytkownika", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Ponowna autoryzacja", diff --git a/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json index ff0f343e2..62b97db79 100644 --- a/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pt/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Atualize suas configurações da caixa de entrada", "AUTO_ASSIGNMENT_SUB_TEXT": "Ativar ou desativar a atribuição automática de novas conversas aos agentes adicionados a essa caixa de entrada.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reautorizar", diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json index fe98bd507..ea1bf9c22 100644 --- a/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Atualize suas configurações de caixa de entrada", "AUTO_ASSIGNMENT_SUB_TEXT": "Ativar ou desativar a atribuição automática de novas conversas aos agentes adicionados a essa caixa de entrada.", "HMAC_VERIFICATION": "Validação de Identidade do Usuário", - "HMAC_DESCRIPTION": "Para validar a identidade dos usuários, o SDK permite que você passe um `identity_hash` para cada usuário. Você pode gerar HMAC usando 'sha256' com a chave mostrada aqui." + "HMAC_DESCRIPTION": "Para validar a identidade dos usuários, o SDK permite que você passe um `identifier_hash` para cada usuário. Você pode gerar HMAC usando 'sha256' com a chave mostrada aqui." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reautorizar", diff --git a/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json index 636b5e4bf..e92b5cf07 100644 --- a/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ro/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Actualizează setările de inbox", "AUTO_ASSIGNMENT_SUB_TEXT": "Activează sau dezactivează atribuirea automată a conversațiilor noi la agenții adăugați la această căsuță poștală.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reautorizează", diff --git a/app/javascript/dashboard/i18n/locale/ru/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ru/inboxMgmt.json index 0590682c6..c6088726e 100644 --- a/app/javascript/dashboard/i18n/locale/ru/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ru/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Изменить настройки источника", "AUTO_ASSIGNMENT_SUB_TEXT": "Включить или отключить автоматическое назначение новых разговоров к агентам, добавленным к этому источнику.", "HMAC_VERIFICATION": "Проверка личности пользователя", - "HMAC_DESCRIPTION": "Для проверки личности пользователя SDK предлагает вам передать параметр `identity_hash` для каждого пользователя. Вы можете сгенерировать HMAC с помощью шифрования 'sha256' с ключом, который указан ниже." + "HMAC_DESCRIPTION": "Для проверки личности пользователя SDK предлагает вам передать параметр `identifier_hash` для каждого пользователя. Вы можете сгенерировать HMAC с помощью шифрования 'sha256' с ключом, который указан ниже." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Войти заново", diff --git a/app/javascript/dashboard/i18n/locale/sk/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/sk/inboxMgmt.json index 7bb14a833..5044b9d1d 100644 --- a/app/javascript/dashboard/i18n/locale/sk/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/sk/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reauthorize", diff --git a/app/javascript/dashboard/i18n/locale/sv/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/sv/inboxMgmt.json index 63d7f5ff9..1cbec9c71 100644 --- a/app/javascript/dashboard/i18n/locale/sv/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/sv/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Uppdatera inställningarna för din inkorg", "AUTO_ASSIGNMENT_SUB_TEXT": "Aktivera eller inaktivera automatisk tilldelning av nya konversationer till de agenter som lagts till den här inkorgen.", "HMAC_VERIFICATION": "Validering av användaridentitet", - "HMAC_DESCRIPTION": "För att validera användarens identitet låter SDKn dig att skicka en `identity_hash` för varje användare. Du kan generera HMAC med 'sha256' med nyckeln som visas här." + "HMAC_DESCRIPTION": "För att validera användarens identitet låter SDKn dig att skicka en `identifier_hash` för varje användare. Du kan generera HMAC med 'sha256' med nyckeln som visas här." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Återauktorisera", diff --git a/app/javascript/dashboard/i18n/locale/ta/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ta/inboxMgmt.json index 043b4d6a0..307bbefc3 100644 --- a/app/javascript/dashboard/i18n/locale/ta/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ta/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "உங்கள் இன்பாக்ஸ் அமைப்புகளைப் புதுப்பிக்கவும்", "AUTO_ASSIGNMENT_SUB_TEXT": "இந்த இன்பாக்ஸில் சேர்க்கப்பட்ட ஏஜென்ட்களுக்கு புதிய உரையாடல்களின் தானியங்கி ஒதுக்கீட்டை இயக்கவும் அல்லது முடக்கவும்.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "மறு அங்கீகாரம்", diff --git a/app/javascript/dashboard/i18n/locale/th/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/th/inboxMgmt.json index 06d6bd7de..7ada5a3a3 100644 --- a/app/javascript/dashboard/i18n/locale/th/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/th/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Reauthorize", diff --git a/app/javascript/dashboard/i18n/locale/tr/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/tr/inboxMgmt.json index d73f0bc8c..a4cfe18a9 100644 --- a/app/javascript/dashboard/i18n/locale/tr/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/tr/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Gelen kutusu ayarlarınızı güncelleyin", "AUTO_ASSIGNMENT_SUB_TEXT": "Bu gelen kutusuna eklenen aracılara yeni görüşmelerin otomatik olarak atanmasını etkinleştirin veya devre dışı bırakın.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Yeniden yetkilendir", diff --git a/app/javascript/dashboard/i18n/locale/uk/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/uk/inboxMgmt.json index 6f4217c12..6b586fc19 100644 --- a/app/javascript/dashboard/i18n/locale/uk/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/uk/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Повторна авторизація", diff --git a/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json index 89916bd3e..f2c09b49c 100644 --- a/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "Cập nhật cài đặt hộp thư đến của bạn", "AUTO_ASSIGNMENT_SUB_TEXT": "Bật hoặc tắt tính năng tự động gán các cuộc hội thoại mới cho các tác nhân được thêm vào hộp thư đến này.", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "Ủy quyền lại", diff --git a/app/javascript/dashboard/i18n/locale/zh_CN/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/zh_CN/inboxMgmt.json index 0a798b4ae..89c23f8f6 100644 --- a/app/javascript/dashboard/i18n/locale/zh_CN/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/zh_CN/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "更新收件箱设置", "AUTO_ASSIGNMENT_SUB_TEXT": "启用或禁用添加到此收件箱的代理人自动分配新的会话。", "HMAC_VERIFICATION": "User Identity Validation", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "重新授权", diff --git a/app/javascript/dashboard/i18n/locale/zh_TW/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/zh_TW/inboxMgmt.json index cf9dda7eb..d65c1e557 100644 --- a/app/javascript/dashboard/i18n/locale/zh_TW/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/zh_TW/inboxMgmt.json @@ -273,7 +273,7 @@ "INBOX_UPDATE_SUB_TEXT": "更新收件匣設定", "AUTO_ASSIGNMENT_SUB_TEXT": "啟用或停用此收件匣客服的對話自動分配。", "HMAC_VERIFICATION": "使用者身份驗證", - "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." + "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." }, "FACEBOOK_REAUTHORIZE": { "TITLE": "重新授權", diff --git a/app/javascript/dashboard/mixins/agentMixin.js b/app/javascript/dashboard/mixins/agentMixin.js index 335390089..295dafa8a 100644 --- a/app/javascript/dashboard/mixins/agentMixin.js +++ b/app/javascript/dashboard/mixins/agentMixin.js @@ -10,17 +10,24 @@ export default { ...mapGetters({ currentUser: 'getCurrentUser', }), + isAgentSelected() { + return this.currentChat?.meta?.assignee; + }, agentsList() { const agents = this.assignableAgents || []; return [ - { - confirmed: true, - name: 'None', - id: 0, - role: 'agent', - account_id: 0, - email: 'None', - }, + ...(this.isAgentSelected + ? [ + { + confirmed: true, + name: 'None', + id: 0, + role: 'agent', + account_id: 0, + email: 'None', + }, + ] + : []), ...agents, ].map(item => item.id === this.currentUser.id diff --git a/app/javascript/dashboard/mixins/specs/agentMixin.spec.js b/app/javascript/dashboard/mixins/specs/agentMixin.spec.js index 41d94e114..7cd2ad0db 100644 --- a/app/javascript/dashboard/mixins/specs/agentMixin.spec.js +++ b/app/javascript/dashboard/mixins/specs/agentMixin.spec.js @@ -24,7 +24,10 @@ describe('agentMixin', () => { title: 'TestComponent', mixins: [agentMixin], data() { - return { inboxId: 1 }; + return { + inboxId: 1, + currentChat: { meta: { assignee: { name: 'John' } } }, + }; }, computed: { assignableAgents() { diff --git a/app/javascript/dashboard/modules/widget-preview/components/Widget.vue b/app/javascript/dashboard/modules/widget-preview/components/Widget.vue new file mode 100644 index 000000000..0f630cbf7 --- /dev/null +++ b/app/javascript/dashboard/modules/widget-preview/components/Widget.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/app/javascript/dashboard/modules/widget-preview/components/WidgetBody.vue b/app/javascript/dashboard/modules/widget-preview/components/WidgetBody.vue new file mode 100644 index 000000000..3bba815db --- /dev/null +++ b/app/javascript/dashboard/modules/widget-preview/components/WidgetBody.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/app/javascript/dashboard/modules/widget-preview/components/WidgetFooter.vue b/app/javascript/dashboard/modules/widget-preview/components/WidgetFooter.vue new file mode 100644 index 000000000..1150b884e --- /dev/null +++ b/app/javascript/dashboard/modules/widget-preview/components/WidgetFooter.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/app/javascript/dashboard/modules/widget-preview/components/WidgetHead.vue b/app/javascript/dashboard/modules/widget-preview/components/WidgetHead.vue new file mode 100644 index 000000000..7857df8a4 --- /dev/null +++ b/app/javascript/dashboard/modules/widget-preview/components/WidgetHead.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/app/javascript/dashboard/modules/widget-preview/stories/Widget.stories.js b/app/javascript/dashboard/modules/widget-preview/stories/Widget.stories.js new file mode 100644 index 000000000..91383eeb6 --- /dev/null +++ b/app/javascript/dashboard/modules/widget-preview/stories/Widget.stories.js @@ -0,0 +1,29 @@ +import Widget from '../components/Widget'; + +const ReplyTime = { + 'In a few minutes': 'in_a_few_minutes', + 'In a few hours': 'in_a_few_hours', + 'In a few day': 'in_a_day', +}; + +export default { + title: 'components/Widget', + component: Widget, + argTypes: { + replyTime: { + control: { + type: 'select', + options: ReplyTime, + }, + }, + }, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { Widget }, + template: '', +}); + +export const DefaultWidget = Template.bind({}); +DefaultWidget.args = {}; diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue index 1f7df7448..93e471a76 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue @@ -30,6 +30,7 @@ color-scheme="success" icon="ion-android-add-circle" @click="onToggleCreate" + data-testid="create-new-contact" > {{ $t('CREATE_CONTACT.BUTTON_LABEL') }} diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactCustomAttributes.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactCustomAttributes.vue index bc3550817..0e2cfa8cb 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ContactCustomAttributes.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactCustomAttributes.vue @@ -14,7 +14,7 @@ {{ attribute }}
    - {{ customAttributes[attribute] }} +
    @@ -22,11 +22,13 @@ diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue index e6cddb584..0231d3e01 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue @@ -24,27 +24,21 @@ - - - {{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }} - + :selected-item="assignedAgent" + :multiselector-title="$t('AGENT_MGMT.MULTI_SELECTOR.TITLE.AGENT')" + :multiselector-placeholder=" + $t('AGENT_MGMT.MULTI_SELECTOR.PLACEHOLDER') + " + :no-search-result=" + $t('AGENT_MGMT.MULTI_SELECTOR.SEARCH.NO_RESULTS.AGENT') + " + :input-placeholder=" + $t('AGENT_MGMT.MULTI_SELECTOR.SEARCH.PLACEHOLDER.AGENT') + " + @click="onClickAssignAgent" + />
    - - {{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }} - + :selected-item="assignedTeam" + :multiselector-title="$t('AGENT_MGMT.MULTI_SELECTOR.TITLE.TEAM')" + :multiselector-placeholder=" + $t('AGENT_MGMT.MULTI_SELECTOR.PLACEHOLDER') + " + :no-search-result=" + $t('AGENT_MGMT.MULTI_SELECTOR.SEARCH.NO_RESULTS.TEAM') + " + :input-placeholder=" + $t('AGENT_MGMT.MULTI_SELECTOR.SEARCH.PLACEHOLDER.TEAM') + " + @click="onClickAssignTeam" + />
    @@ -138,7 +134,7 @@ import ContactDetailsItem from './ContactDetailsItem.vue'; import ContactInfo from './contact/ContactInfo'; import ConversationLabels from './labels/LabelBox.vue'; import ContactCustomAttributes from './ContactCustomAttributes'; -import AvailabilityStatusBadge from 'dashboard/components/widgets/conversation/AvailabilityStatusBadge.vue'; +import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue'; import flag from 'country-code-emoji'; @@ -149,7 +145,7 @@ export default { ContactDetailsItem, ContactInfo, ConversationLabels, - AvailabilityStatusBadge, + MultiselectDropdown, }, mixins: [alertMixin, agentMixin], props: { @@ -195,9 +191,8 @@ export default { return this.additionalAttributes.initiated_at; }, browserName() { - return `${this.browser.browser_name || ''} ${ - this.browser.browser_version || '' - }`; + return `${this.browser.browser_name || ''} ${this.browser + .browser_version || ''}`; }, contactAdditionalAttributes() { return this.contact.additional_attributes || {}; @@ -331,6 +326,21 @@ export default { }; this.assignedAgent = selfAssign; }, + onClickAssignAgent(selectedItem) { + if (this.assignedAgent && this.assignedAgent.id === selectedItem.id) { + this.assignedAgent = null; + } else { + this.assignedAgent = selectedItem; + } + }, + + onClickAssignTeam(selectedItemTeam) { + if (this.assignedTeam && this.assignedTeam.id === selectedItemTeam.id) { + this.assignedTeam = null; + } else { + this.assignedTeam = selectedItemTeam; + } + }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/labels/LabelBox.vue b/app/javascript/dashboard/routes/dashboard/conversation/labels/LabelBox.vue index 162f853ed..40d33d865 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/labels/LabelBox.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/labels/LabelBox.vue @@ -9,7 +9,11 @@ icon="ion-pricetags" emoji="🏷️" /> -
    +
    - Search Results + {{ $t('CONVERSATION.SEARCH.RESULT_TITLE') }} ({{ resultsCount }}) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/AddCampaign.vue b/app/javascript/dashboard/routes/dashboard/settings/campaigns/AddCampaign.vue similarity index 69% rename from app/javascript/dashboard/routes/dashboard/settings/inbox/components/AddCampaign.vue rename to app/javascript/dashboard/routes/dashboard/settings/campaigns/AddCampaign.vue index 5bd4c84e4..cf56f66e9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/AddCampaign.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/campaigns/AddCampaign.vue @@ -16,7 +16,21 @@ @blur="$v.title.$touch" /> -