Compare commits
81
Commits
v3.0.0
...
upgrade-fb-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe4bc2833 | ||
|
|
e9950afb1a | ||
|
|
f21298aeb9 | ||
|
|
7b6a56714f | ||
|
|
94a20af9db | ||
|
|
616371adbb | ||
|
|
2d4ef0c328 | ||
|
|
5e6e234afe | ||
|
|
18e9e771b5 | ||
|
|
0245e01763 | ||
|
|
1c9993e01b | ||
|
|
fcd40e9636 | ||
|
|
4f5c5e9f85 | ||
|
|
cb07ac16d1 | ||
|
|
4fa299ff15 | ||
|
|
71e9566854 | ||
|
|
a1a59684cd | ||
|
|
272f920811 | ||
|
|
e39d19b1e8 | ||
|
|
6c39aed882 | ||
|
|
8d43101892 | ||
|
|
f05535c5ad | ||
|
|
4503ba018d | ||
|
|
8bc2bc6d40 | ||
|
|
bfd192ebb2 | ||
|
|
6e13d22ff7 | ||
|
|
3e8b6e1aee | ||
|
|
3038aaef67 | ||
|
|
57a68666cb | ||
|
|
4acb824857 | ||
|
|
5c74674c2b | ||
|
|
52e49d5f9d | ||
|
|
e529e1206e | ||
|
|
a8009c7b39 | ||
|
|
99bb074656 | ||
|
|
9322112481 | ||
|
|
ce4cfee8bd | ||
|
|
f31fc2b375 | ||
|
|
e5f7807833 | ||
|
|
2483d99d3c | ||
|
|
336584c95a | ||
|
|
9ebabb9832 | ||
|
|
2acf09b3eb | ||
|
|
d18d3e9e2e | ||
|
|
deec1d213b | ||
|
|
25c3fb3c36 | ||
|
|
e2a6dc3e04 | ||
|
|
57feedbf25 | ||
|
|
71310c21ad | ||
|
|
623cd72897 | ||
|
|
04aa13e8aa | ||
|
|
6ab964b161 | ||
|
|
5598b4b27e | ||
|
|
64ae9f625a | ||
|
|
3dd3b7b3aa | ||
|
|
f451e59aec | ||
|
|
c04f67e8a5 | ||
|
|
6ddc99d066 | ||
|
|
4cbb3208e8 | ||
|
|
5f2a720920 | ||
|
|
acb7debd3f | ||
|
|
44f6a9ec3c | ||
|
|
24468d71fb | ||
|
|
26ef21a243 | ||
|
|
18235d3fb5 | ||
|
|
55972cc17a | ||
|
|
2daf1ae827 | ||
|
|
22421b5ac8 | ||
|
|
0d41853b0b | ||
|
|
355926897a | ||
|
|
4101a7b566 | ||
|
|
2d31535620 | ||
|
|
c54b469dcb | ||
|
|
29e8e41443 | ||
|
|
178bc80b25 | ||
|
|
ab039e14fd | ||
|
|
961c911c26 | ||
|
|
12ae49089b | ||
|
|
996f842882 | ||
|
|
616f702871 | ||
|
|
8015490aba |
@@ -15,9 +15,9 @@ defaults: &defaults
|
||||
- image: cimg/postgres:15.3
|
||||
- image: cimg/redis:6.2.6
|
||||
environment:
|
||||
- RAILS_LOG_TO_STDOUT: false
|
||||
- COVERAGE: true
|
||||
- LOG_LEVEL: warn
|
||||
- RAILS_LOG_TO_STDOUT: false
|
||||
- COVERAGE: true
|
||||
- LOG_LEVEL: warn
|
||||
parallelism: 4
|
||||
resource_class: large
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
nvm install v16
|
||||
nvm install v20
|
||||
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
|
||||
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
|
||||
|
||||
@@ -64,7 +64,6 @@ jobs:
|
||||
- ~/.bundle
|
||||
key: chatwoot-bundle-{{ .Environment.CACHE_VERSION }}-v20220524-{{ checksum "Gemfile.lock" }}
|
||||
|
||||
|
||||
# Only necessary if app uses webpacker or yarn in some other way
|
||||
- restore_cache:
|
||||
keys:
|
||||
@@ -82,7 +81,7 @@ jobs:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- run:
|
||||
name: Download cc-test-reporter
|
||||
name: Download cc-test-reporter
|
||||
command: |
|
||||
mkdir -p ~/tmp
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ~/tmp/cc-test-reporter
|
||||
@@ -182,7 +181,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ~/build
|
||||
- run:
|
||||
name: Download cc-test-reporter
|
||||
name: Download cc-test-reporter
|
||||
command: |
|
||||
mkdir -p ~/tmp
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ~/tmp/cc-test-reporter
|
||||
@@ -204,4 +203,4 @@ workflows:
|
||||
- build
|
||||
- upload-coverage:
|
||||
requires:
|
||||
- build
|
||||
- build
|
||||
|
||||
@@ -199,6 +199,8 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
||||
## Rack Attack configuration
|
||||
## To prevent and throttle abusive requests
|
||||
# ENABLE_RACK_ATTACK=true
|
||||
# RACK_ATTACK_IP_LIMIT=3000
|
||||
# ENABLE_RACK_ATTACK_WIDGET_API=true
|
||||
|
||||
## Running chatwoot as an API only server
|
||||
## setting this value to true will disable the frontend dashboard endpoints
|
||||
|
||||
@@ -52,7 +52,8 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
@@ -75,6 +76,8 @@ jobs:
|
||||
- name: Run backend tests
|
||||
run: |
|
||||
bundle exec rspec --profile=10 --format documentation
|
||||
env:
|
||||
NODE_OPTIONS: --openssl-legacy-provider
|
||||
|
||||
- name: Upload rails log folder
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -51,7 +51,8 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
|
||||
@@ -21,7 +21,8 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: 'yarn'
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
@@ -30,9 +31,11 @@ jobs:
|
||||
run: |
|
||||
rm -rf enterprise
|
||||
rm -rf spec/enterprise
|
||||
|
||||
|
||||
- name: Run asset compile
|
||||
run: bundle exec rake assets:precompile
|
||||
env:
|
||||
NODE_OPTIONS: --openssl-legacy-provider
|
||||
|
||||
- name: Size Check
|
||||
run: yarn run size
|
||||
|
||||
+4
-9
@@ -8,15 +8,11 @@ Layout/LineLength:
|
||||
Max: 150
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 125
|
||||
Max: 175
|
||||
Exclude:
|
||||
- 'app/models/conversation.rb'
|
||||
- 'app/models/contact.rb'
|
||||
- 'app/mailers/conversation_reply_mailer.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'app/builders/messages/facebook/message_builder.rb'
|
||||
- 'app/controllers/api/v1/accounts/contacts_controller.rb'
|
||||
- 'app/listeners/action_cable_listener.rb'
|
||||
- 'app/models/conversation.rb'
|
||||
|
||||
RSpec/ExampleLength:
|
||||
Max: 25
|
||||
Style/Documentation:
|
||||
@@ -73,7 +69,7 @@ Rails/ApplicationController:
|
||||
- 'app/controllers/survey/responses_controller.rb'
|
||||
Rails/FindEach:
|
||||
Enabled: true
|
||||
Include:
|
||||
Include:
|
||||
- 'app/**/*.rb'
|
||||
Rails/CompactBlank:
|
||||
Enabled: false
|
||||
@@ -189,7 +185,6 @@ RSpec/IndexedLet:
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
|
||||
|
||||
# we should bring this down
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 14
|
||||
|
||||
@@ -4,7 +4,7 @@ ruby '3.2.2'
|
||||
|
||||
##-- base gems for rails --##
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
gem 'rails', '~> 7.0.5.1'
|
||||
gem 'rails', '~> 7.0.8.0'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
@@ -108,14 +108,14 @@ gem 'elastic-apm', require: false
|
||||
gem 'newrelic_rpm', require: false
|
||||
gem 'newrelic-sidekiq-metrics', require: false
|
||||
gem 'scout_apm', require: false
|
||||
gem 'sentry-rails', '>= 5.10.0', require: false
|
||||
gem 'sentry-rails', '>= 5.11.0', require: false
|
||||
gem 'sentry-ruby', require: false
|
||||
gem 'sentry-sidekiq', '>= 5.10.0', require: false
|
||||
gem 'sentry-sidekiq', '>= 5.11.0', require: false
|
||||
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq', '>= 7.1.3'
|
||||
# We want cron jobs
|
||||
gem 'sidekiq-cron'
|
||||
gem 'sidekiq-cron', '>= 1.10.1'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'fcm'
|
||||
@@ -188,7 +188,7 @@ group :development do
|
||||
gem 'bullet'
|
||||
gem 'letter_opener'
|
||||
gem 'scss_lint', require: false
|
||||
gem 'web-console'
|
||||
gem 'web-console', '>= 4.2.1'
|
||||
|
||||
# used in swagger build
|
||||
gem 'json_refs'
|
||||
|
||||
+81
-81
@@ -33,70 +33,70 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actioncable (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionmailbox (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionmailer (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionpack (7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actiontext (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionview (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activejob (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activerecord (7.0.5.1)
|
||||
activemodel (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activemodel (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activerecord (7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activestorage (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activestorage (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.5.1)
|
||||
activesupport (7.0.8)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@@ -269,8 +269,8 @@ GEM
|
||||
grpc (~> 1.36)
|
||||
geocoder (1.8.1)
|
||||
gli (2.21.0)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
gmail_xoauth (0.4.2)
|
||||
oauth (>= 0.3.6)
|
||||
google-apis-core (0.11.0)
|
||||
@@ -450,9 +450,9 @@ GEM
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.4)
|
||||
minitest (5.19.0)
|
||||
minitest (5.20.0)
|
||||
mock_redis (0.36.0)
|
||||
ruby2_keywords
|
||||
msgpack (1.7.0)
|
||||
@@ -463,7 +463,7 @@ GEM
|
||||
activerecord (>= 5.2)
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.3.6)
|
||||
net-imap (0.3.7)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -478,14 +478,14 @@ GEM
|
||||
sidekiq
|
||||
newrelic_rpm (8.16.0)
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.3)
|
||||
nokogiri (1.15.4)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.3-arm64-darwin)
|
||||
nokogiri (1.15.4-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.3-x86_64-darwin)
|
||||
nokogiri (1.15.4-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.3-x86_64-linux)
|
||||
nokogiri (1.15.4-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
numo-narray (0.9.2.1)
|
||||
oauth (1.1.0)
|
||||
@@ -543,7 +543,7 @@ GEM
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (5.0.1)
|
||||
puma (6.2.2)
|
||||
puma (6.3.1)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -563,30 +563,30 @@ GEM
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rack-timeout (0.6.3)
|
||||
rails (7.0.5.1)
|
||||
actioncable (= 7.0.5.1)
|
||||
actionmailbox (= 7.0.5.1)
|
||||
actionmailer (= 7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
actiontext (= 7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activemodel (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
rails (7.0.8)
|
||||
actioncable (= 7.0.8)
|
||||
actionmailbox (= 7.0.8)
|
||||
actionmailer (= 7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actiontext (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.5.1)
|
||||
rails-dom-testing (2.1.1)
|
||||
railties (= 7.0.8)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
railties (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
railties (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
@@ -598,7 +598,7 @@ GEM
|
||||
ffi (~> 1.0)
|
||||
redis (5.0.6)
|
||||
redis-client (>= 0.9.0)
|
||||
redis-client (0.14.1)
|
||||
redis-client (0.17.0)
|
||||
connection_pool
|
||||
redis-namespace (1.10.0)
|
||||
redis (>= 4)
|
||||
@@ -697,23 +697,23 @@ GEM
|
||||
activesupport (>= 4)
|
||||
selectize-rails (0.12.6)
|
||||
semantic_range (3.0.0)
|
||||
sentry-rails (5.10.0)
|
||||
sentry-rails (5.11.0)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.10.0)
|
||||
sentry-ruby (5.10.0)
|
||||
sentry-ruby (~> 5.11.0)
|
||||
sentry-ruby (5.11.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sentry-sidekiq (5.10.0)
|
||||
sentry-ruby (~> 5.10.0)
|
||||
sentry-sidekiq (5.11.0)
|
||||
sentry-ruby (~> 5.11.0)
|
||||
sidekiq (>= 3.0)
|
||||
sexp_processor (4.17.0)
|
||||
shoulda-matchers (5.3.0)
|
||||
activesupport (>= 5.2.0)
|
||||
sidekiq (7.1.2)
|
||||
sidekiq (7.1.3)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.14.0)
|
||||
sidekiq-cron (1.10.0)
|
||||
sidekiq-cron (1.10.1)
|
||||
fugit (~> 1.8)
|
||||
globalid (>= 1.0.1)
|
||||
sidekiq (>= 6)
|
||||
@@ -786,7 +786,7 @@ GEM
|
||||
version_gem (1.1.2)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.0)
|
||||
web-console (4.2.1)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
@@ -805,14 +805,14 @@ GEM
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.8.1)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
wisper (2.0.0)
|
||||
working_hours (1.4.1)
|
||||
activesupport (>= 3.2)
|
||||
tzinfo
|
||||
zeitwerk (2.6.9)
|
||||
zeitwerk (2.6.11)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-20
|
||||
@@ -907,7 +907,7 @@ DEPENDENCIES
|
||||
rack-cors
|
||||
rack-mini-profiler (>= 3.1.1)
|
||||
rack-timeout
|
||||
rails (~> 7.0.5.1)
|
||||
rails (~> 7.0.8.0)
|
||||
redis
|
||||
redis-namespace
|
||||
responders
|
||||
@@ -922,12 +922,12 @@ DEPENDENCIES
|
||||
scout_apm
|
||||
scss_lint
|
||||
seed_dump
|
||||
sentry-rails (>= 5.10.0)
|
||||
sentry-rails (>= 5.11.0)
|
||||
sentry-ruby
|
||||
sentry-sidekiq (>= 5.10.0)
|
||||
sentry-sidekiq (>= 5.11.0)
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
sidekiq-cron
|
||||
sidekiq (>= 7.1.3)
|
||||
sidekiq-cron (>= 1.10.1)
|
||||
simplecov (= 0.17.1)
|
||||
slack-ruby-client (~> 2.0.0)
|
||||
spring
|
||||
@@ -943,7 +943,7 @@ DEPENDENCIES
|
||||
tzinfo-data
|
||||
uglifier
|
||||
valid_email2
|
||||
web-console
|
||||
web-console (>= 4.2.1)
|
||||
web-push
|
||||
webmock
|
||||
webpacker
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
backend: bin/rails s -p 3000
|
||||
frontend: bin/webpack-dev-server
|
||||
frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
|
||||
# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
|
||||
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
@@ -60,6 +60,9 @@
|
||||
],
|
||||
"stack": "heroku-20",
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/nodejs"
|
||||
},
|
||||
{
|
||||
"url": "heroku/ruby"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
before_action :portal
|
||||
before_action :check_authorization
|
||||
before_action :fetch_article, except: [:index, :create, :attach_file, :reorder]
|
||||
before_action :fetch_article, except: [:index, :create, :reorder]
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
def index
|
||||
@@ -36,17 +36,6 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:background_image].tempfile,
|
||||
filename: params[:background_image].original_filename,
|
||||
content_type: params[:background_image].content_type
|
||||
)
|
||||
file_blob.save!
|
||||
render json: { file_url: url_for(file_blob) }
|
||||
end
|
||||
|
||||
def reorder
|
||||
Article.update_positions(params[:positions_hash])
|
||||
head :ok
|
||||
|
||||
@@ -20,16 +20,6 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
|
||||
@automation_rule
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:attachment].tempfile,
|
||||
filename: params[:attachment].original_filename,
|
||||
content_type: params[:attachment].content_type
|
||||
)
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
automation_rule_update
|
||||
|
||||
@@ -18,7 +18,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
|
||||
def index
|
||||
@contacts_count = resolved_contacts.count
|
||||
@contacts = fetch_contacts_with_conversation_count(resolved_contacts)
|
||||
@contacts = fetch_contacts(resolved_contacts)
|
||||
end
|
||||
|
||||
def search
|
||||
@@ -29,7 +29,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
search: "%#{params[:q].strip}%"
|
||||
)
|
||||
@contacts_count = contacts.count
|
||||
@contacts = fetch_contacts_with_conversation_count(contacts)
|
||||
@contacts = fetch_contacts(contacts)
|
||||
end
|
||||
|
||||
def import
|
||||
@@ -63,7 +63,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
result = ::Contacts::FilterService.new(params.permit!, current_user).perform
|
||||
contacts = result[:contacts]
|
||||
@contacts_count = result[:count]
|
||||
@contacts = fetch_contacts_with_conversation_count(contacts)
|
||||
@contacts = fetch_contacts(contacts)
|
||||
end
|
||||
|
||||
def contactable_inboxes
|
||||
@@ -125,17 +125,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
@current_page = params[:page] || 1
|
||||
end
|
||||
|
||||
def fetch_contacts_with_conversation_count(contacts)
|
||||
conversation_count_sub_query = 'SELECT COUNT(*) FROM "conversations" WHERE "conversations"."contact_id" = "contacts"."id"'
|
||||
contacts_with_conversation_count = filtrate(contacts)
|
||||
.select("contacts.*, (#{conversation_count_sub_query}) as conversations_count")
|
||||
.group('contacts.id')
|
||||
.includes([{ avatar_attachment: [:blob] }])
|
||||
.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
def fetch_contacts(contacts)
|
||||
contacts_with_avatar = filtrate(contacts)
|
||||
.includes([{ avatar_attachment: [:blob] }])
|
||||
.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
|
||||
return contacts_with_conversation_count.includes([{ contact_inboxes: [:inbox] }]) if @include_contact_inboxes
|
||||
return contacts_with_avatar.includes([{ contact_inboxes: [:inbox] }]) if @include_contact_inboxes
|
||||
|
||||
contacts_with_conversation_count
|
||||
contacts_with_avatar
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
|
||||
@@ -39,16 +39,6 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:attachment].tempfile,
|
||||
filename: params[:attachment].original_filename,
|
||||
content_type: params[:attachment].content_type
|
||||
)
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
def execute
|
||||
::MacrosExecutionJob.perform_later(@macro, conversation_ids: params[:conversation_ids], user: Current.user)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||
include ::FileTypeHelper
|
||||
|
||||
before_action :fetch_portal, except: [:index, :create, :attach_file]
|
||||
before_action :fetch_portal, except: [:index, :create]
|
||||
before_action :check_authorization
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
@@ -53,16 +53,6 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||
@portal.logo.attach(blob)
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:logo].tempfile,
|
||||
filename: params[:logo].original_filename,
|
||||
content_type: params[:logo].content_type
|
||||
)
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_portal
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class Api::V1::UploadController < Api::BaseController
|
||||
def create
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:attachment].tempfile,
|
||||
filename: params[:attachment].original_filename,
|
||||
content_type: params[:attachment].content_type
|
||||
)
|
||||
file_blob.save!
|
||||
|
||||
render json: { file_url: url_for(file_blob), blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ module EnsureCurrentAccountHelper
|
||||
|
||||
def ensure_current_account
|
||||
account = Account.find(params[:account_id])
|
||||
ensure_account_is_active?(account)
|
||||
render_unauthorized('Account is suspended') and return unless account.active?
|
||||
|
||||
if current_user
|
||||
account_accessible_for_user?(account)
|
||||
@@ -27,8 +27,4 @@ module EnsureCurrentAccountHelper
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
|
||||
def ensure_account_is_active?(account)
|
||||
render_unauthorized('Account is suspended') unless account.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,7 +55,7 @@ class DashboardController < ActionController::Base
|
||||
VAPID_PUBLIC_KEY: VapidService.public_key,
|
||||
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
|
||||
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
|
||||
FACEBOOK_API_VERSION: 'v14.0',
|
||||
FACEBOOK_API_VERSION: 'v17.0',
|
||||
IS_ENTERPRISE: ChatwootApp.enterprise?,
|
||||
AZURE_APP_ID: ENV.fetch('AZURE_APP_ID', ''),
|
||||
GIT_SHA: GIT_HASH
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Public::Api::V1::Portals::BaseController < PublicController
|
||||
before_action :show_plain_layout
|
||||
around_action :set_locale
|
||||
after_action :allow_iframe_requests
|
||||
|
||||
private
|
||||
|
||||
@@ -39,4 +40,8 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
|
||||
def allow_iframe_requests
|
||||
response.headers.delete('X-Frame-Options') if @is_plain_layout_enabled
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
v-if="!authUIFlags.isFetching"
|
||||
id="app"
|
||||
class="app-wrapper app-root"
|
||||
class="app-wrapper h-full flex-grow-0 min-h-0 w-full"
|
||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||
:dir="isRTLView ? 'rtl' : 'ltr'"
|
||||
>
|
||||
|
||||
@@ -9,14 +9,6 @@ class AutomationsAPI extends ApiClient {
|
||||
clone(automationId) {
|
||||
return axios.post(`${this.url}/${automationId}/clone`);
|
||||
}
|
||||
|
||||
attachment(file) {
|
||||
return axios.post(`${this.url}/attach_file`, file, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new AutomationsAPI();
|
||||
|
||||
@@ -47,20 +47,6 @@ class ArticlesAPI extends PortalsAPI {
|
||||
return axios.delete(`${this.url}/${portalSlug}/articles/${articleId}`);
|
||||
}
|
||||
|
||||
uploadImage({ portalSlug, file }) {
|
||||
let formData = new FormData();
|
||||
formData.append('background_image', file);
|
||||
return axios.post(
|
||||
`${this.url}/${portalSlug}/articles/attach_file`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
reorderArticles({ portalSlug, reorderedGroup, categorySlug }) {
|
||||
return axios.post(`${this.url}/${portalSlug}/articles/reorder`, {
|
||||
positions_hash: reorderedGroup,
|
||||
|
||||
@@ -26,7 +26,7 @@ code {
|
||||
background: $color-background;
|
||||
border-radius: var(--border-radius-large);
|
||||
padding: $space-two;
|
||||
@apply bg-slate-50 dark:bg-slate-600 text-slate-800 dark:text-slate-100;
|
||||
@apply bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,87 +9,25 @@ body {
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
@include full-height;
|
||||
flex-grow: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
@apply h-full flex-grow-0 min-h-0 w-full;
|
||||
|
||||
.button--fixed-top {
|
||||
@apply fixed ltr:right-2 rtl:left-2 top-2 flex flex-row;
|
||||
}
|
||||
}
|
||||
|
||||
.banner + .app-wrapper {
|
||||
// Reduce the height of the dashboard to make room for the banner.
|
||||
// And causing the top right green-action button to be pushed down when scrolling.
|
||||
@apply h-[calc(100%-48px)];
|
||||
|
||||
.button--fixed-top {
|
||||
top: 5.6 * $space-one;
|
||||
@apply top-14;
|
||||
}
|
||||
|
||||
.off-canvas-content {
|
||||
.button--fixed-top {
|
||||
top: $space-small;
|
||||
@apply top-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is-closed .app-root {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.view-box {
|
||||
@include full-height;
|
||||
@include space-between-column;
|
||||
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.view-panel {
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
padding: $space-normal;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
overflow: auto;
|
||||
padding: $space-normal;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
@include flex;
|
||||
|
||||
align-items: center;
|
||||
color: $color-woot;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-normal;
|
||||
margin-right: $space-normal;
|
||||
|
||||
&::before {
|
||||
font-size: $font-size-large;
|
||||
margin-right: $space-small;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.button-spinner {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.no-items-error-message {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
max-width: $space-mega;
|
||||
padding: $space-one;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
.app-rtl--wrapper {
|
||||
direction: rtl;
|
||||
|
||||
.header-section.back-button {
|
||||
direction: initial;
|
||||
margin-left: var(--space-normal);
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
|
||||
// Settings header action button
|
||||
.button--fixed-top {
|
||||
left: $space-small;
|
||||
position: fixed;
|
||||
right: unset;
|
||||
top: $space-small;
|
||||
}
|
||||
|
||||
// Woot Tabs
|
||||
.tabs-title {
|
||||
&:first-child {
|
||||
|
||||
@@ -71,9 +71,3 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.button--fixed-top {
|
||||
position: fixed;
|
||||
right: var(--space-small);
|
||||
top: var(--space-small);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.button {
|
||||
@apply items-center inline-flex h-10 mb-0;
|
||||
@apply items-center inline-flex h-10 mb-0 gap-2;
|
||||
|
||||
.button__content {
|
||||
@apply w-full;
|
||||
@@ -14,17 +14,9 @@
|
||||
@apply px-2 py-0;
|
||||
}
|
||||
|
||||
.icon--emoji + .button__content {
|
||||
@apply pl-2 rtl:pr-2 rtl:pl-0;
|
||||
}
|
||||
|
||||
.icon--font + .button__content {
|
||||
@apply pl-2 rtl:pr-2 rtl:pl-0;
|
||||
}
|
||||
|
||||
// @TODDO - Remove after moving all buttons to woot-button
|
||||
.icon + .button__content {
|
||||
@apply pl-2 w-auto rtl:pr-2 rtl:pl-0;
|
||||
@apply w-auto;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
@@ -157,18 +149,10 @@
|
||||
// Sizes
|
||||
&.tiny {
|
||||
@apply h-6;
|
||||
|
||||
.icon + .button__content {
|
||||
@apply pl-1 rtl:pr-1 rtl:pl-0;
|
||||
}
|
||||
}
|
||||
|
||||
&.small {
|
||||
@apply h-8 pb-1 pt-1;
|
||||
|
||||
.icon + .button__content {
|
||||
@apply pl-1 rtl:pr-1 rtl:pl-0;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
|
||||
@@ -76,13 +76,13 @@ table {
|
||||
|
||||
.ve-pagination-goto {
|
||||
@apply text-slate-600 dark:text-slate-200;
|
||||
|
||||
.ve-pagination-goto-input {
|
||||
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200;
|
||||
}
|
||||
}
|
||||
|
||||
.ve-pagination-li {
|
||||
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200 border-slate-75 dark:border-slate-700;
|
||||
}
|
||||
|
||||
.ve-pagination-goto-input {
|
||||
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
>
|
||||
<a
|
||||
v-if="isAttributeTypeLink"
|
||||
:href="value"
|
||||
:href="hrefURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
@@ -188,6 +188,9 @@ export default {
|
||||
urlValue() {
|
||||
return isValidURL(this.value) ? this.value : '---';
|
||||
},
|
||||
hrefURL() {
|
||||
return isValidURL(this.value) ? this.value : '';
|
||||
},
|
||||
notAttributeTypeCheckboxAndList() {
|
||||
return !this.isAttributeTypeCheckbox && !this.isAttributeTypeList;
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div
|
||||
v-if="show"
|
||||
v-on-clickaway="onClickAway"
|
||||
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-20 rounded-md shadow-xl bg-white dark:bg-slate-800 py-2 px-2 border border-slate-25 dark:border-slate-700"
|
||||
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-30 rounded-md shadow-xl bg-white dark:bg-slate-800 py-2 px-2 border border-slate-25 dark:border-slate-700"
|
||||
:class="{ 'block visible': show }"
|
||||
>
|
||||
<availability-status />
|
||||
|
||||
@@ -46,6 +46,7 @@ import AgentDetails from './AgentDetails';
|
||||
import NotificationBell from './NotificationBell';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import { ACCOUNT_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -99,6 +100,7 @@ export default {
|
||||
window.$chatwoot.toggle();
|
||||
},
|
||||
openNotificationPanel() {
|
||||
this.$track(ACCOUNT_EVENTS.OPENED_NOTIFICATIONS);
|
||||
this.$emit('open-notification-panel');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="banner" :class="bannerClasses">
|
||||
<div
|
||||
class="banner flex items-center h-12 gap-4 text-white dark:text-white text-xs py-3 px-4 justify-center"
|
||||
:class="bannerClasses"
|
||||
>
|
||||
<span class="banner-message">
|
||||
{{ bannerMessage }}
|
||||
<a
|
||||
@@ -96,8 +99,6 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.banner {
|
||||
@apply flex items-center gap-4 text-white dark:text-white text-xs py-3 px-4 justify-center;
|
||||
|
||||
&.primary {
|
||||
@apply bg-woot-500 dark:bg-woot-500;
|
||||
.banner-action__button {
|
||||
|
||||
@@ -61,11 +61,9 @@ export default {
|
||||
this.label = this.$t('AUTOMATION.ATTACHMENT.LABEL_UPLOADING');
|
||||
try {
|
||||
const file = event.target.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('attachment', file, file.name);
|
||||
const id = await this.$store.dispatch(
|
||||
'automations/uploadAttachment',
|
||||
formData
|
||||
file
|
||||
);
|
||||
this.$emit('input', [id]);
|
||||
this.uploadState = 'uploaded';
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<button class="header-section back-button" @click.capture="goBack">
|
||||
<button
|
||||
class="header-section flex items-center text-base font-normal mr-4 ml-2 cursor-pointer text-woot-500 dark:text-woot-500"
|
||||
@click.capture="goBack"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" />
|
||||
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex items-center text-left">
|
||||
<div class="flex items-center gap-1.5 text-left">
|
||||
<thumbnail
|
||||
:src="user.thumbnail"
|
||||
:size="size"
|
||||
@@ -7,7 +7,7 @@
|
||||
:status="user.availability_status"
|
||||
/>
|
||||
<h6
|
||||
class="my-0 mx-2 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
|
||||
class="my-0 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
|
||||
:class="textClass"
|
||||
>
|
||||
{{ user.name }}
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
:search-key="variableSearchTerm"
|
||||
@click="insertVariable"
|
||||
/>
|
||||
<input
|
||||
ref="imageUpload"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
hidden
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,7 +29,7 @@
|
||||
<script>
|
||||
import {
|
||||
messageSchema,
|
||||
wootMessageWriterSetup,
|
||||
buildEditor,
|
||||
EditorView,
|
||||
MessageMarkdownTransformer,
|
||||
MessageMarkdownSerializer,
|
||||
@@ -39,6 +46,7 @@ import CannedResponse from '../conversation/CannedResponse';
|
||||
import VariableList from '../conversation/VariableList';
|
||||
|
||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
|
||||
|
||||
import {
|
||||
hasPressedEnterAndNotCmdOrShift,
|
||||
@@ -51,14 +59,27 @@ import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
||||
import { replaceVariablesInMessage } from '@chatwoot/utils';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import { uploadFile } from 'dashboard/helper/uploadHelper';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { findNodeToInsertImage } from 'dashboard/helper/messageEditorHelper';
|
||||
import { MESSAGE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||
|
||||
const createState = (content, placeholder, plugins = []) => {
|
||||
const createState = (
|
||||
content,
|
||||
placeholder,
|
||||
plugins = [],
|
||||
methods = {},
|
||||
enabledMenuOptions
|
||||
) => {
|
||||
return EditorState.create({
|
||||
doc: new MessageMarkdownTransformer(messageSchema).parse(content),
|
||||
plugins: wootMessageWriterSetup({
|
||||
plugins: buildEditor({
|
||||
schema: messageSchema,
|
||||
placeholder,
|
||||
methods,
|
||||
plugins,
|
||||
enabledMenuOptions,
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -66,7 +87,7 @@ const createState = (content, placeholder, plugins = []) => {
|
||||
export default {
|
||||
name: 'WootMessageEditor',
|
||||
components: { TagAgents, CannedResponse, VariableList },
|
||||
mixins: [eventListenerMixins, uiSettingsMixin],
|
||||
mixins: [eventListenerMixins, uiSettingsMixin, alertMixin],
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
editorId: { type: String, default: '' },
|
||||
@@ -78,6 +99,7 @@ export default {
|
||||
enableVariables: { type: Boolean, default: false },
|
||||
enableCannedResponses: { type: Boolean, default: true },
|
||||
variables: { type: Object, default: () => ({}) },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -104,6 +126,11 @@ export default {
|
||||
this.enableCannedResponses && this.showCannedMenu && !this.isPrivate
|
||||
);
|
||||
},
|
||||
editorMenuOptions() {
|
||||
return this.enabledMenuOptions.length
|
||||
? this.enabledMenuOptions
|
||||
: MESSAGE_EDITOR_MENU_OPTIONS;
|
||||
},
|
||||
plugins() {
|
||||
if (!this.enableSuggestions) {
|
||||
return [];
|
||||
@@ -204,18 +231,18 @@ export default {
|
||||
showVariables(updatedValue) {
|
||||
this.$emit('toggle-variables-menu', !this.isPrivate && updatedValue);
|
||||
},
|
||||
value(newValue = '') {
|
||||
if (newValue !== this.contentFromEditor) {
|
||||
this.reloadState();
|
||||
value(newVal = '') {
|
||||
if (newVal !== this.contentFromEditor) {
|
||||
this.reloadState(newVal);
|
||||
}
|
||||
},
|
||||
editorId() {
|
||||
this.showCannedMenu = false;
|
||||
this.cannedSearchTerm = '';
|
||||
this.reloadState();
|
||||
this.reloadState(this.value);
|
||||
},
|
||||
isPrivate() {
|
||||
this.reloadState();
|
||||
this.reloadState(this.value);
|
||||
},
|
||||
|
||||
updateSelectionWith(newValue, oldValue) {
|
||||
@@ -238,7 +265,13 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||
this.state = createState(
|
||||
this.value,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.editorMenuOptions
|
||||
);
|
||||
},
|
||||
mounted() {
|
||||
this.createEditorView();
|
||||
@@ -246,8 +279,14 @@ export default {
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
methods: {
|
||||
reloadState() {
|
||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||
reloadState(content = this.value) {
|
||||
this.state = createState(
|
||||
content,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.editorMenuOptions
|
||||
);
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
@@ -374,6 +413,57 @@ export default {
|
||||
tr.scrollIntoView();
|
||||
return false;
|
||||
},
|
||||
openFileBrowser() {
|
||||
this.$refs.imageUpload.click();
|
||||
},
|
||||
onFileChange() {
|
||||
const file = this.$refs.imageUpload.files[0];
|
||||
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
||||
this.uploadImageToStorage(file);
|
||||
} else {
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SIZE_ERROR',
|
||||
{
|
||||
size: MAXIMUM_FILE_UPLOAD_SIZE,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.$refs.imageUpload.value = '';
|
||||
},
|
||||
async uploadImageToStorage(file) {
|
||||
try {
|
||||
const { fileUrl } = await uploadFile(file);
|
||||
if (fileUrl) {
|
||||
this.onImageInsertInEditor(fileUrl);
|
||||
}
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SUCCESS'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_ERROR'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
onImageInsertInEditor(fileUrl) {
|
||||
const { tr } = this.editorView.state;
|
||||
|
||||
const insertData = findNodeToInsertImage(this.editorView.state, fileUrl);
|
||||
|
||||
if (insertData) {
|
||||
this.editorView.dispatch(
|
||||
tr.insert(insertData.pos, insertData.node).scrollIntoView()
|
||||
);
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
},
|
||||
|
||||
emitOnChange() {
|
||||
this.editorView.updateState(this.state);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<script>
|
||||
import {
|
||||
fullSchema,
|
||||
wootArticleWriterSetup,
|
||||
buildEditor,
|
||||
EditorView,
|
||||
ArticleMarkdownSerializer,
|
||||
ArticleMarkdownTransformer,
|
||||
@@ -33,15 +33,17 @@ const createState = (
|
||||
content,
|
||||
placeholder,
|
||||
plugins = [],
|
||||
onImageUpload = () => {}
|
||||
methods = {},
|
||||
enabledMenuOptions
|
||||
) => {
|
||||
return EditorState.create({
|
||||
doc: new ArticleMarkdownTransformer(fullSchema).parse(content),
|
||||
plugins: wootArticleWriterSetup({
|
||||
plugins: buildEditor({
|
||||
schema: fullSchema,
|
||||
placeholder,
|
||||
methods,
|
||||
plugins,
|
||||
onImageUpload,
|
||||
enabledMenuOptions,
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -52,6 +54,7 @@ export default {
|
||||
value: { type: String, default: '' },
|
||||
editorId: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -83,7 +86,8 @@ export default {
|
||||
this.value,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
this.openFileBrowser
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.enabledMenuOptions
|
||||
);
|
||||
},
|
||||
mounted() {
|
||||
@@ -152,7 +156,8 @@ export default {
|
||||
this.value,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
this.openFileBrowser
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.enabledMenuOptions
|
||||
);
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
|
||||
+1
@@ -358,6 +358,7 @@ export default {
|
||||
applied_filters: this.appliedFilters.map(filter => ({
|
||||
key: filter.attribute_key,
|
||||
operator: filter.filter_operator,
|
||||
query_operator: filter.query_operator,
|
||||
})),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
<div
|
||||
class="flex justify-center items-center mr-4 rtl:mr-0 rtl:ml-4 min-w-0"
|
||||
>
|
||||
<back-button v-if="showBackButton" :back-url="backButtonUrl" />
|
||||
<back-button
|
||||
v-if="showBackButton"
|
||||
:back-url="backButtonUrl"
|
||||
class="ltr:ml-0 rtl:mr-0 rtl:ml-4"
|
||||
/>
|
||||
<Thumbnail
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
|
||||
@@ -725,6 +725,15 @@ export default {
|
||||
this.sendMessage(messagePayload);
|
||||
});
|
||||
},
|
||||
sendMessageAnalyticsData(isPrivate) {
|
||||
// Analytics data for message signature is enabled or not in channels
|
||||
return isPrivate
|
||||
? this.$track(CONVERSATION_EVENTS.SENT_PRIVATE_NOTE)
|
||||
: this.$track(CONVERSATION_EVENTS.SENT_MESSAGE, {
|
||||
channelType: this.channelType,
|
||||
signatureEnabled: this.sendWithSignature,
|
||||
});
|
||||
},
|
||||
async onSendReply() {
|
||||
const undefinedVariables = getUndefinedVariablesInMessage({
|
||||
message: this.message,
|
||||
@@ -758,6 +767,7 @@ export default {
|
||||
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
bus.$emit(BUS_EVENTS.MESSAGE_SENT);
|
||||
this.removeFromDraft();
|
||||
this.sendMessageAnalyticsData(messagePayload.private);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error?.response?.data?.error || this.$t('CONVERSATION.MESSAGE_ERROR');
|
||||
|
||||
@@ -95,7 +95,7 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mention--box {
|
||||
@apply bg-white text-sm dark:bg-slate-700 rounded-md overflow-auto absolute w-full z-[100] pt-2 px-2 pb-0 shadow-md left-0 leading-[1.2] bottom-full max-h-[12.5rem] border-t border-solid border-slate-75 dark:border-slate-800;
|
||||
@apply bg-white text-sm dark:bg-slate-700 rounded-md overflow-auto absolute w-full z-20 pt-2 px-2 pb-0 shadow-md left-0 leading-[1.2] bottom-full max-h-[12.5rem] border-t border-solid border-slate-75 dark:border-slate-800;
|
||||
|
||||
&.with-bottom-border {
|
||||
@apply border-b-[0.5rem] border-solid border-white dark:border-slate-600;
|
||||
|
||||
@@ -79,7 +79,7 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mention--box {
|
||||
@apply bg-white dark:bg-slate-700 rounded-md overflow-auto absolute w-full z-[100] pt-2 px-2 pb-0 shadow-md left-0 bottom-full max-h-[9.75rem] border-t border-solid border-slate-75 dark:border-slate-800;
|
||||
@apply bg-white dark:bg-slate-700 rounded-md overflow-auto absolute w-full z-20 pt-2 px-2 pb-0 shadow-md left-0 bottom-full max-h-[9.75rem] border-t border-solid border-slate-75 dark:border-slate-800;
|
||||
|
||||
.dropdown-menu__item:last-child {
|
||||
@apply pb-1;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
export const MESSAGE_EDITOR_MENU_OPTIONS = [
|
||||
'strong',
|
||||
'em',
|
||||
'link',
|
||||
'undo',
|
||||
'redo',
|
||||
'bulletList',
|
||||
'orderedList',
|
||||
'code',
|
||||
];
|
||||
|
||||
export const MESSAGE_SIGNATURE_EDITOR_MENU_OPTIONS = [
|
||||
'strong',
|
||||
'em',
|
||||
'link',
|
||||
'undo',
|
||||
'redo',
|
||||
'imageUpload',
|
||||
];
|
||||
|
||||
export const ARTICLE_EDITOR_MENU_OPTIONS = [
|
||||
'strong',
|
||||
'em',
|
||||
'link',
|
||||
'undo',
|
||||
'redo',
|
||||
'bulletList',
|
||||
'orderedList',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'imageUpload',
|
||||
'code',
|
||||
];
|
||||
@@ -15,6 +15,9 @@ export const ACCOUNT_EVENTS = Object.freeze({
|
||||
ADDED_A_CUSTOM_ATTRIBUTE: 'Added a custom attribute',
|
||||
ADDED_AN_INBOX: 'Added an inbox',
|
||||
OPEN_MESSAGE_CONTEXT_MENU: 'Opened message context menu',
|
||||
OPENED_NOTIFICATIONS: 'Opened notifications',
|
||||
MARK_AS_READ_NOTIFICATIONS: 'Marked notifications as read',
|
||||
OPEN_CONVERSATION_VIA_NOTIFICATION: 'Opened conversation via notification',
|
||||
});
|
||||
|
||||
export const LABEL_EVENTS = Object.freeze({
|
||||
|
||||
@@ -120,9 +120,18 @@ export const generateConditionOptions = (options, key = 'id') => {
|
||||
});
|
||||
};
|
||||
|
||||
// Add the "None" option to the agent list
|
||||
export const agentList = agents => [
|
||||
{
|
||||
id: 'nil',
|
||||
name: 'None',
|
||||
},
|
||||
...(agents || []),
|
||||
];
|
||||
|
||||
export const getActionOptions = ({ agents, teams, labels, type }) => {
|
||||
const actionsMap = {
|
||||
assign_agent: agents,
|
||||
assign_agent: agentList(agents),
|
||||
assign_team: teams,
|
||||
send_email_to_team: teams,
|
||||
add_label: generateConditionOptions(labels, 'title'),
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Determines the appropriate node and position to insert an image in the editor.
|
||||
*
|
||||
* Based on the current editor state and the provided image URL, this function finds out the correct node (either
|
||||
* a standalone image node or an image wrapped in a paragraph) and its respective position in the editor.
|
||||
*
|
||||
* 1. If the current node is a paragraph and doesn't contain an image or text, the image is inserted directly into it.
|
||||
* 2. If the current node isn't a paragraph or it's a paragraph containing text, the image will be wrapped
|
||||
* in a new paragraph and then inserted.
|
||||
* 3. If the current node is a paragraph containing an image, the new image will be inserted directly into it.
|
||||
*
|
||||
* @param {Object} editorState - The current state of the editor. It provides necessary details like selection, schema, etc.
|
||||
* @param {string} fileUrl - The URL of the image to be inserted into the editor.
|
||||
* @returns {Object|null} An object containing details about the node to be inserted and its position. It returns null if no image node can be created.
|
||||
* @property {Node} node - The ProseMirror node to be inserted (either an image node or a paragraph containing the image).
|
||||
* @property {number} pos - The position where the new node should be inserted in the editor.
|
||||
*/
|
||||
|
||||
export const findNodeToInsertImage = (editorState, fileUrl) => {
|
||||
const { selection, schema } = editorState;
|
||||
const { nodes } = schema;
|
||||
const currentNode = selection.$from.node();
|
||||
const {
|
||||
type: { name: typeName },
|
||||
content: { size, content },
|
||||
} = currentNode;
|
||||
|
||||
const imageNode = nodes.image.create({ src: fileUrl });
|
||||
|
||||
if (!imageNode) return null;
|
||||
|
||||
const isInParagraph = typeName === 'paragraph';
|
||||
const needsNewLine =
|
||||
!content.some(n => n.type.name === 'image') && size !== 0 ? 1 : 0;
|
||||
|
||||
return {
|
||||
node: isInParagraph ? imageNode : nodes.paragraph.create({}, imageNode),
|
||||
pos: selection.from + needsNewLine,
|
||||
};
|
||||
};
|
||||
@@ -49,3 +49,38 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
|
||||
// Proceed to the route if none of the above conditions are met
|
||||
return null;
|
||||
};
|
||||
|
||||
export const isAConversationRoute = routeName =>
|
||||
[
|
||||
'inbox_conversation',
|
||||
'conversation_through_mentions',
|
||||
'conversation_through_unattended',
|
||||
'conversation_through_inbox',
|
||||
'conversations_through_label',
|
||||
'conversations_through_team',
|
||||
'conversations_through_folders',
|
||||
'conversation_through_participating',
|
||||
].includes(routeName);
|
||||
|
||||
export const getConversationDashboardRoute = routeName => {
|
||||
switch (routeName) {
|
||||
case 'inbox_conversation':
|
||||
return 'home';
|
||||
case 'conversation_through_mentions':
|
||||
return 'conversation_mentions';
|
||||
case 'conversation_through_unattended':
|
||||
return 'conversation_unattended';
|
||||
case 'conversations_through_label':
|
||||
return 'label_conversations';
|
||||
case 'conversations_through_team':
|
||||
return 'team_conversations';
|
||||
case 'conversations_through_folders':
|
||||
return 'folder_conversations';
|
||||
case 'conversation_through_participating':
|
||||
return 'conversation_participating';
|
||||
case 'conversation_through_inbox':
|
||||
return 'inbox_dashboard';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { findNodeToInsertImage } from '../messageEditorHelper';
|
||||
|
||||
describe('findNodeToInsertImage', () => {
|
||||
let mockEditorState;
|
||||
|
||||
beforeEach(() => {
|
||||
mockEditorState = {
|
||||
selection: {
|
||||
$from: {
|
||||
node: jest.fn(() => ({})),
|
||||
},
|
||||
from: 0,
|
||||
},
|
||||
schema: {
|
||||
nodes: {
|
||||
image: {
|
||||
create: jest.fn(attrs => ({ type: { name: 'image' }, attrs })),
|
||||
},
|
||||
paragraph: {
|
||||
create: jest.fn((_, node) => ({
|
||||
type: { name: 'paragraph' },
|
||||
content: [node],
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should insert image directly into an empty paragraph', () => {
|
||||
const mockNode = {
|
||||
type: { name: 'paragraph' },
|
||||
content: { size: 0, content: [] },
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result).toEqual({
|
||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
||||
pos: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert image directly into a paragraph without an image but with other content', () => {
|
||||
const mockNode = {
|
||||
type: { name: 'paragraph' },
|
||||
content: {
|
||||
size: 1,
|
||||
content: [
|
||||
{
|
||||
type: { name: 'text' },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
mockEditorState.selection.from = 1;
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result).toEqual({
|
||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
||||
pos: 2, // Because it should insert after the text, on a new line.
|
||||
});
|
||||
});
|
||||
|
||||
it("should wrap image in a new paragraph when the current node isn't a paragraph", () => {
|
||||
const mockNode = {
|
||||
type: { name: 'not-a-paragraph' },
|
||||
content: { size: 0, content: [] },
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result.node.type.name).toBe('paragraph');
|
||||
expect(result.node.content[0].type.name).toBe('image');
|
||||
expect(result.node.content[0].attrs.src).toBe('image-url');
|
||||
expect(result.pos).toBe(0);
|
||||
});
|
||||
|
||||
it('should insert a new image directly into the paragraph that already contains an image', () => {
|
||||
const mockNode = {
|
||||
type: { name: 'paragraph' },
|
||||
content: {
|
||||
size: 1,
|
||||
content: [
|
||||
{
|
||||
type: { name: 'image', attrs: { src: 'existing-image-url' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
mockEditorState.selection.from = 1;
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result.node.type.name).toBe('image');
|
||||
expect(result.node.attrs.src).toBe('image-url');
|
||||
expect(result.pos).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
getConversationDashboardRoute,
|
||||
getCurrentAccount,
|
||||
getUserRole,
|
||||
isAConversationRoute,
|
||||
routeIsAccessibleFor,
|
||||
validateLoggedInRoutes,
|
||||
} from '../routeHelpers';
|
||||
@@ -94,3 +96,41 @@ describe('#validateLoggedInRoutes', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAConversationRoute', () => {
|
||||
it('returns true if conversation route name is provided', () => {
|
||||
expect(isAConversationRoute('inbox_conversation')).toBe(true);
|
||||
expect(isAConversationRoute('conversation_through_inbox')).toBe(true);
|
||||
expect(isAConversationRoute('conversations_through_label')).toBe(true);
|
||||
expect(isAConversationRoute('conversations_through_team')).toBe(true);
|
||||
expect(isAConversationRoute('dashboard')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConversationDashboardRoute', () => {
|
||||
it('returns dashboard route for conversation', () => {
|
||||
expect(getConversationDashboardRoute('inbox_conversation')).toEqual('home');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversation_through_mentions')
|
||||
).toEqual('conversation_mentions');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversation_through_unattended')
|
||||
).toEqual('conversation_unattended');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversations_through_label')
|
||||
).toEqual('label_conversations');
|
||||
expect(getConversationDashboardRoute('conversations_through_team')).toEqual(
|
||||
'team_conversations'
|
||||
);
|
||||
expect(
|
||||
getConversationDashboardRoute('conversations_through_folders')
|
||||
).toEqual('folder_conversations');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversation_through_participating')
|
||||
).toEqual('conversation_participating');
|
||||
expect(getConversationDashboardRoute('conversation_through_inbox')).toEqual(
|
||||
'inbox_dashboard'
|
||||
);
|
||||
expect(getConversationDashboardRoute('non_existent_route')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { uploadFile } from '../uploadHelper';
|
||||
import axios from 'axios';
|
||||
|
||||
// Mocking axios using jest-mock-axios
|
||||
global.axios = axios;
|
||||
jest.mock('axios');
|
||||
|
||||
describe('#Upload Helpers', () => {
|
||||
afterEach(() => {
|
||||
// Cleaning up the mock after each test
|
||||
axios.post.mockReset();
|
||||
});
|
||||
|
||||
it('should send a POST request with correct data', async () => {
|
||||
const mockFile = new File(['dummy content'], 'example.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const mockResponse = {
|
||||
data: {
|
||||
file_url: 'https://example.com/fileUrl',
|
||||
blob_key: 'blobKey123',
|
||||
blob_id: 'blobId456',
|
||||
},
|
||||
};
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
const result = await uploadFile(mockFile);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'/api/v1/upload',
|
||||
expect.any(FormData),
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
fileUrl: 'https://example.com/fileUrl',
|
||||
blobKey: 'blobKey123',
|
||||
blobId: 'blobId456',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle errors', async () => {
|
||||
const mockFile = new File(['dummy content'], 'example.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const mockError = new Error('Failed to upload');
|
||||
|
||||
axios.post.mockRejectedValueOnce(mockError);
|
||||
|
||||
await expect(uploadFile(mockFile)).rejects.toThrow('Failed to upload');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/* global axios */
|
||||
|
||||
/**
|
||||
* Constants and Configuration
|
||||
*/
|
||||
|
||||
// Version for the API endpoint.
|
||||
const API_VERSION = 'v1';
|
||||
|
||||
// Default headers to be used in the axios request.
|
||||
const HEADERS = {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a file to the server.
|
||||
*
|
||||
* This function sends a POST request to a given API endpoint and uploads the specified file.
|
||||
* The function uses FormData to wrap the file and axios to send the request.
|
||||
*
|
||||
* @param {File} file - The file to be uploaded. It should be a File object (typically coming from a file input element).
|
||||
* @returns {Promise} A promise that resolves with the server's response when the upload is successful, or rejects if there's an error.
|
||||
*/
|
||||
export async function uploadFile(file) {
|
||||
// Create a new FormData instance.
|
||||
let formData = new FormData();
|
||||
|
||||
// Append the file to the FormData instance under the key 'attachment'.
|
||||
formData.append('attachment', file);
|
||||
|
||||
// Use axios to send a POST request to the upload endpoint.
|
||||
const { data } = await axios.post(`/api/${API_VERSION}/upload`, formData, {
|
||||
headers: HEADERS,
|
||||
});
|
||||
|
||||
return {
|
||||
fileUrl: data.file_url,
|
||||
blobKey: data.blob_key,
|
||||
blobId: data.blob_id,
|
||||
};
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import pt_BR from './locale/pt_BR';
|
||||
import ro from './locale/ro';
|
||||
import ru from './locale/ru';
|
||||
import sk from './locale/sk';
|
||||
import sr from './locale/sr';
|
||||
import sv from './locale/sv';
|
||||
import ta from './locale/ta';
|
||||
import th from './locale/th';
|
||||
@@ -66,6 +67,7 @@ export default {
|
||||
ro,
|
||||
ru,
|
||||
sk,
|
||||
sr,
|
||||
sv,
|
||||
ta,
|
||||
th,
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Import",
|
||||
"CANCEL": "Cancel"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Contacts saved successfully",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "There was an error, please try again"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Update",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Cancel"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Need help?",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "استيراد",
|
||||
"CANCEL": "إلغاء"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "تم حفظ جهة الاتصال بنجاح",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "حدث خطأ، الرجاء المحاولة مرة أخرى"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "تحديث",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "إلغاء"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "تحتاج مساعدة؟",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "إنشاء توقيع رسالة شخصية يتم إضافتها إلى جميع الرسائل التي ترسلها من المنصة. استخدم محرر المحتوى الغني لإنشاء توقيع شديد التخصيص.",
|
||||
"BTN_TEXT": "حفظ توقيع الرسالة",
|
||||
"API_ERROR": "تعذر إرسال الرسالة! حاول مرة أخرى",
|
||||
"API_SUCCESS": "تم حفظ التوقيع بنجاح"
|
||||
"API_SUCCESS": "تم حفظ التوقيع بنجاح",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "توقيع الرسالة",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Внасяне",
|
||||
"CANCEL": "Отмени"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Успешно запазване на контактите",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "Възникна грешка, моля опитайте отново"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Обновяване",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Отмени"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Need help?",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importa",
|
||||
"CANCEL": "Cancel·la"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Contactes desat correctament",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Actualitza",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Cancel·la"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Need help?",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Import",
|
||||
"CANCEL": "Zrušit"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Contacts saved successfully",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "Došlo k chybě, zkuste to prosím znovu"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Aktualizovat",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Zrušit"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Need help?",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importer",
|
||||
"CANCEL": "Annuller"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Kontakter gemt",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "Der opstod en fejl. Prøv venligst igen"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Opdater",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Annuller"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Brug for hjælp?",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Opret en personlig besked signatur, der vil blive føjet til alle de meddelelser, du sender fra din e-mail indbakke. Brug den rige content editor til at oprette en meget personlig signatur.",
|
||||
"BTN_TEXT": "Gem beskedsignatur",
|
||||
"API_ERROR": "Kunne ikke gemme signatur! Prøv igen",
|
||||
"API_SUCCESS": "Signatur gemt"
|
||||
"API_SUCCESS": "Signatur gemt",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Besked Signatur",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importieren",
|
||||
"CANCEL": "Abbrechen"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Kontakte erfolgreich gespeichert",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "Es ist ein Fehler aufgetreten, bitte versuchen Sie es erneut"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Aktualisieren",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Stornieren"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Brauchen Sie Hilfe?",
|
||||
"DISMISS": "Verwerfen",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Mit KI verbessern",
|
||||
"SUMMARY_TITLE": "Zusammenfassung mit KI",
|
||||
"REPLY_TITLE": "Vorschlag mit KI beantworten",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Erstellen Sie eine persönliche Nachrichtensignatur, die allen Nachrichten hinzugefügt wird, die Sie aus Ihrem E-Mail-Posteingang senden. Verwenden Sie den Rich-Content-Editor, um eine stark personalisierte Signatur zu erstellen.",
|
||||
"BTN_TEXT": "Nachrichten-Signatur speichern",
|
||||
"API_ERROR": "Signatur konnte nicht gespeichert werden! Versuch es noch einmal",
|
||||
"API_SUCCESS": "Signatur erfolgreich gespeichert"
|
||||
"API_SUCCESS": "Signatur erfolgreich gespeichert",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Bildgröße sollte kleiner als {size}MB sein"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Nachrichten-Signatur",
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Εισαγωγή",
|
||||
"CANCEL": "Άκυρο"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Οι επαφές αποθηκεύτηκαν με επιτυχία",
|
||||
"SUCCESS_MESSAGE": "Θα ειδοποιηθείτε μέσω email όταν ολοκληρωθεί η εισαγωγή.",
|
||||
"ERROR_MESSAGE": "Υπήρξε ένα σφάλμα, παρακαλώ προσπαθήστε ξανά"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
"UPDATE": "Ενημέρωση",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
@@ -124,6 +125,18 @@
|
||||
"CANCEL": "Άκυρο"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Χρειάζεστε βοήθεια;",
|
||||
"DISMISS": "Dismiss",
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Improve With AI",
|
||||
"SUMMARY_TITLE": "Summary with AI",
|
||||
"REPLY_TITLE": "Reply suggestion with AI",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Δημιουργήστε μια προσωπική υπογραφή, η οποία θα προστεθεί σε όλα τα μηνύματα που στέλνετε από τα εισερχόμενα email σας. Χρησιμοποιήστε τον επεξεργαστή πλούσιου περιεχομένου για να δημιουργήσετε μια εξατομικευμένη υπογραφή.",
|
||||
"BTN_TEXT": "Αποθήκευση υπογραφής μηνύματος",
|
||||
"API_ERROR": "Δεν ήταν δυνατή η αποθήκευση της υπογραφής! Δοκιμάστε ξανά",
|
||||
"API_SUCCESS": "Η υπογραφή αποθηκεύτηκε με επιτυχία"
|
||||
"API_SUCCESS": "Η υπογραφή αποθηκεύτηκε με επιτυχία",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Υπογραφή μηνύματος",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Import",
|
||||
"CANCEL": "Cancel"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Contacts saved successfully",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "There was an error, please try again"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
"UPDATE": "Update",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -56,8 +56,16 @@
|
||||
"EDIT": "%{agentName} actualizó una macro (#%{id})",
|
||||
"DELETE": "%{agentName} eliminó una macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} agregó %{user} a la bandeja de entrada (#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} eliminó %{user} de la bandeja de entrada(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} agregó %{user} al equipo (#%{team_id})",
|
||||
"REMOVE": "%{agentName} eliminó %{user} del equipo (#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
"EDIT": "%{agentName} Actualizó la configuración de la cuenta (#%{id})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importar",
|
||||
"CANCEL": "Cancelar"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Contacto guardado correctamente",
|
||||
"SUCCESS_MESSAGE": "Se le notificará por correo electrónico cuando se complete la importación.",
|
||||
"ERROR_MESSAGE": "Hubo un error, por favor inténtelo de nuevo"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"NO_INBOX_AGENT": "¡Uh Oh! Parece que no eres parte de ninguna bandeja de entrada. Por favor, contacta con tu administrador",
|
||||
"SEARCH_MESSAGES": "Buscar mensajes en conversaciones",
|
||||
"EMPTY_STATE": {
|
||||
"CMD_BAR": "to open command menu",
|
||||
"KEYBOARD_SHORTCUTS": "to view keyboard shortcuts"
|
||||
"CMD_BAR": "para abrir el menú de comandos",
|
||||
"KEYBOARD_SHORTCUTS": "para ver los atajos del teclado"
|
||||
},
|
||||
"SEARCH": {
|
||||
"TITLE": "Buscar mensajes",
|
||||
|
||||
@@ -111,8 +111,8 @@
|
||||
"ADD_LABEL": "Añadir etiqueta a la conversación",
|
||||
"REMOVE_LABEL": "Eliminar etiqueta de la conversación",
|
||||
"SETTINGS": "Ajustes",
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"APPEARANCE": "Appearance"
|
||||
"AI_ASSIST": "Asistencia AI",
|
||||
"APPEARANCE": "Apariencia"
|
||||
},
|
||||
"COMMANDS": {
|
||||
"GO_TO_CONVERSATION_DASHBOARD": "Ir al panel de conversaciones",
|
||||
@@ -134,7 +134,7 @@
|
||||
"GO_TO_NOTIFICATIONS": "Ir a Notificaciones",
|
||||
"ADD_LABELS_TO_CONVERSATION": "Añadir etiqueta a la conversación",
|
||||
"ASSIGN_AN_AGENT": "Asignar un agente",
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"AI_ASSIST": "Asistencia AI",
|
||||
"ASSIGN_PRIORITY": "Asignar prioridad",
|
||||
"ASSIGN_A_TEAM": "Asignar un equipo",
|
||||
"MUTE_CONVERSATION": "Silenciar conversación",
|
||||
@@ -150,9 +150,9 @@
|
||||
"UNTIL_NEXT_MONTH": "Hasta el mes próximo",
|
||||
"AN_HOUR_FROM_NOW": "Hasta una hora a partir de ahora",
|
||||
"CUSTOM": "Personalizar...",
|
||||
"CHANGE_APPEARANCE": "Change Appearance",
|
||||
"LIGHT_MODE": "Light",
|
||||
"DARK_MODE": "Dark",
|
||||
"CHANGE_APPEARANCE": "Cambiar apariencia",
|
||||
"LIGHT_MODE": "Claro",
|
||||
"DARK_MODE": "Oscuro",
|
||||
"SYSTEM_MODE": "Sistema"
|
||||
}
|
||||
},
|
||||
@@ -160,7 +160,7 @@
|
||||
"LOADING_MESSAGE": "Cargando aplicación del tablero..."
|
||||
},
|
||||
"COMMON": {
|
||||
"OR": "Or",
|
||||
"OR": "O",
|
||||
"CLICK_HERE": "haz clic aquí"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +112,14 @@
|
||||
"ERROR": "Este campo es obligatorio"
|
||||
},
|
||||
"API_KEY": {
|
||||
"USE_API_KEY": "Use API Key Authentication",
|
||||
"USE_API_KEY": "Usar autenticación de Clave API",
|
||||
"LABEL": "API Key SID",
|
||||
"PLACEHOLDER": "Please enter your API Key SID",
|
||||
"PLACEHOLDER": "Por favor ingrese su API Key SID",
|
||||
"ERROR": "Este campo es obligatorio"
|
||||
},
|
||||
"API_KEY_SECRET": {
|
||||
"LABEL": "API Key Secret",
|
||||
"PLACEHOLDER": "Please enter your API Key Secret",
|
||||
"LABEL": "Clave API secreta",
|
||||
"PLACEHOLDER": "Por favor ingrese su Clave API secreta",
|
||||
"ERROR": "Este campo es obligatorio"
|
||||
},
|
||||
"MESSAGING_SERVICE_SID": {
|
||||
@@ -403,21 +403,21 @@
|
||||
"DISABLED": "Deshabilitado"
|
||||
},
|
||||
"SENDER_NAME_SECTION": {
|
||||
"TITLE": "Sender name",
|
||||
"SUB_TEXT": "Select the name shown to the your customer when they receive emails from your agents.",
|
||||
"FOR_EG": "For eg:",
|
||||
"TITLE": "Nombre del remitente",
|
||||
"SUB_TEXT": "Seleccione el nombre que se muestra al cliente cuando reciba correos electrónicos de sus agentes.",
|
||||
"FOR_EG": "Por ejemplo:",
|
||||
"FRIENDLY": {
|
||||
"TITLE": "Amigable",
|
||||
"FROM": "De",
|
||||
"SUBTITLE": "Add the name of the agent who sent the reply in the sender name to make it friendly."
|
||||
"SUBTITLE": "Añada el nombre del agente que envió la respuesta en el nombre del remitente para que sea amistoso."
|
||||
},
|
||||
"PROFESSIONAL": {
|
||||
"TITLE": "Profesional",
|
||||
"SUBTITLE": "Use only the configured business name as the sender name in the email header."
|
||||
"SUBTITLE": "Utilice sólo el nombre del negocio configurado como nombre del remitente en el encabezado del correo electrónico."
|
||||
},
|
||||
"BUSINESS_NAME": {
|
||||
"BUTTON_TEXT": "+ Configure your business name",
|
||||
"PLACEHOLDER": "Enter your business name",
|
||||
"BUTTON_TEXT": "+ Configura el nombre de tu negocio",
|
||||
"PLACEHOLDER": "Introduce el nombre de tu negocio",
|
||||
"SAVE_BUTTON_TEXT": "Guardar"
|
||||
}
|
||||
},
|
||||
@@ -484,9 +484,9 @@
|
||||
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Activar o desactivar la caja de recolección de correo electrónico",
|
||||
"AUTO_ASSIGNMENT": "Activar asignación automática",
|
||||
"ENABLE_CSAT": "Habilitar Encuesta de Satisfacción",
|
||||
"SENDER_NAME_SECTION": "Enable Agent Name in Email",
|
||||
"SENDER_NAME_SECTION": "Habilitar nombre del agente en el correo electrónico",
|
||||
"ENABLE_CSAT_SUB_TEXT": "Habilitar/deshabilitar encuesta CSAT(satisfacción del cliente) después de resolver una conversación",
|
||||
"SENDER_NAME_SECTION_TEXT": "Enable/Disable showing Agent's name in email, if disabled it will show business name",
|
||||
"SENDER_NAME_SECTION_TEXT": "Habilitar/Deshabilitar mostrando el nombre del agente en el correo electrónico, si está deshabilitado, mostrará el nombre del negocio",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL": "Habilitar continuidad de conversación por correo electrónico",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Las conversaciones continuarán por correo electrónico si la dirección de correo electrónico de contacto está disponible.",
|
||||
"LOCK_TO_SINGLE_CONVERSATION": "Bloquear a una sola conversación",
|
||||
|
||||
@@ -75,24 +75,25 @@
|
||||
"SLACK": {
|
||||
"DELETE": "Eliminar",
|
||||
"DELETE_CONFIRMATION": {
|
||||
"TITLE": "Delete the integration",
|
||||
"MESSAGE": "Are you sure you want to delete the integration? Doing so will result in the loss of access to conversations on your Slack workspace."
|
||||
"TITLE": "Eliminar la integración",
|
||||
"MESSAGE": "¿Está seguro que desea eliminar la integración? Si lo hace, perderá el acceso a las conversaciones en su espacio de trabajo Slack."
|
||||
},
|
||||
"HELP_TEXT": {
|
||||
"TITLE": "Utilizando integración de Slack",
|
||||
"TITLE": "¿Cómo utilizar la Integración Slack?",
|
||||
"BODY": "<br/><p>Chatwoot ahora sincronizará todas las conversaciones entrantes en el canal <b><i>de conversaciones del cliente</i></b> dentro de tu lugar de trabajo slack.</p><p>Respondiendo a un tema de conversación en <b><i>conversaciones de clientes</i></b> canal de slack creará una respuesta al cliente a través de chatwoot.</p><p>Inicie las respuestas con <b><i>nota:</i></b> para crear notas privadas en lugar de respuestas.</p><p>Si el respondente de slack tiene un perfil de agente en el chatwoot bajo el mismo correo electrónico, las respuestas se asociarán en consecuencia.</p><p>Cuando el replicador no tiene un perfil de agente asociado, las respuestas se harán con el perfil del bot.</p>",
|
||||
"SELECTED": "selected"
|
||||
"SELECTED": "seleccionado"
|
||||
},
|
||||
"SELECT_CHANNEL": {
|
||||
"OPTION_LABEL": "Select a channel",
|
||||
"OPTION_LABEL": "Seleccione un canal",
|
||||
"UPDATE": "Actualizar",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required"
|
||||
"BUTTON_TEXT": "Conectar canal",
|
||||
"DESCRIPTION": "Su espacio de trabajo de Slack ahora esta enlazado con Chatwoot. Sin embarbo, la integración esta actualmente inactiva. Para activar la integración y conectar un canal a Chatwoot, por favor haga clic en el siguiente boton.\n\n**Nota.** Si esta intentando conectar un canal privado, agregue la aplicación Chatwoot al canal de Slack antes de proceder con este paso.",
|
||||
"ATTENTION_REQUIRED": "Atención requerida",
|
||||
"EXPIRED": "Su integración de Slack ha caducado. Para continuar recibiendo mensajes en Slack, por favor elimine la integración y vuelva a conectar su área de trabajo."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
"FAILED_TO_FETCH_CHANNELS": "There was an error fetching the channels from Slack, please try again"
|
||||
"UPDATE_ERROR": "Se presento un error actualizando la integración, por favor inténtelo nuevamente",
|
||||
"UPDATE_SUCCESS": "El canal está conectado correctamente",
|
||||
"FAILED_TO_FETCH_CHANNELS": "Hubo un error obteniendo los canales de Slack, por favor inténtalo de nuevo"
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Haga clic aquí para unirse",
|
||||
@@ -102,28 +103,40 @@
|
||||
"CREATE_ERROR": "Hubo un error al crear un enlace de reunión, por favor inténtelo de nuevo"
|
||||
},
|
||||
"OPEN_AI": {
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"WITH_AI": " %{option} with AI ",
|
||||
"AI_ASSIST": "Asistencia AI",
|
||||
"WITH_AI": " %{option} con IA ",
|
||||
"OPTIONS": {
|
||||
"REPLY_SUGGESTION": "Reply Suggestion",
|
||||
"SUMMARIZE": "Summarize",
|
||||
"REPHRASE": "Improve Writing",
|
||||
"FIX_SPELLING_GRAMMAR": "Fix Spelling and Grammar",
|
||||
"SHORTEN": "Shorten",
|
||||
"EXPAND": "Expand",
|
||||
"MAKE_FRIENDLY": "Change message tone to friendly",
|
||||
"MAKE_FORMAL": "Use formal tone",
|
||||
"SIMPLIFY": "Simplify"
|
||||
"REPLY_SUGGESTION": "Responder sugerencia",
|
||||
"SUMMARIZE": "Resumir",
|
||||
"REPHRASE": "Mejorar escritura",
|
||||
"FIX_SPELLING_GRAMMAR": "Corregir ortografía y gramática",
|
||||
"SHORTEN": "Acortar",
|
||||
"EXPAND": "Expandir",
|
||||
"MAKE_FRIENDLY": "Cambiar tono de mensaje a amigable",
|
||||
"MAKE_FORMAL": "Usar tono formal",
|
||||
"SIMPLIFY": "Simplificar"
|
||||
},
|
||||
"ASSISTANCE_MODAL": {
|
||||
"DRAFT_TITLE": "Draft content",
|
||||
"GENERATED_TITLE": "Generated content",
|
||||
"AI_WRITING": "AI is writing",
|
||||
"DRAFT_TITLE": "Contenido de borrador",
|
||||
"GENERATED_TITLE": "Contenido generado",
|
||||
"AI_WRITING": "AI está escribiendo",
|
||||
"BUTTONS": {
|
||||
"APPLY": "Use this suggestion",
|
||||
"APPLY": "Usar esta sugerencia",
|
||||
"CANCEL": "Cancelar"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrar con OpenAI",
|
||||
"DESC": "Trae las características avanzadas de IA a tu panel de control con los modelos GPT de OpenAI. Para empezar, introduce la clave API desde tu cuenta OpenAI.",
|
||||
"KEY_PLACEHOLDER": "Introduzca su clave API OpenAI",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "¿Necesitas ayuda?",
|
||||
"DISMISS": "Descartar",
|
||||
"FINISH": "Finalizar configuración"
|
||||
},
|
||||
"DISMISS_MESSAGE": "Puedes configurar la integración OpenAI más tarde cuando quieras.",
|
||||
"SUCCESS_MESSAGE": "Configuración de integración OpenAI exitosa"
|
||||
},
|
||||
"TITLE": "Mejorar con IA",
|
||||
"SUMMARY_TITLE": "Resumen con IA",
|
||||
"REPLY_TITLE": "Responder sugerencia con IA",
|
||||
@@ -140,7 +153,7 @@
|
||||
"GENERATING": "Generando...",
|
||||
"CANCEL": "Cancelar"
|
||||
},
|
||||
"GENERATE_ERROR": "Hubo un error al procesar el contenido, por favor inténtelo de nuevo"
|
||||
"GENERATE_ERROR": "Hubo un error al procesar el contenido, por favor verifique su clave API OpenAI e inténtelo de nuevo"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Eliminar",
|
||||
|
||||
@@ -40,16 +40,16 @@
|
||||
},
|
||||
"SUGGESTIONS": {
|
||||
"TOOLTIP": {
|
||||
"SINGLE_SUGGESTION": "Add label to conversation",
|
||||
"MULTIPLE_SUGGESTION": "Select this label",
|
||||
"DESELECT": "Deselect label",
|
||||
"DISMISS": "Dismiss suggestion"
|
||||
"SINGLE_SUGGESTION": "Añadir etiqueta a la conversación",
|
||||
"MULTIPLE_SUGGESTION": "Seleccionar esta etiqueta",
|
||||
"DESELECT": "Deseleccionar etiqueta",
|
||||
"DISMISS": "Descartar sugerencia"
|
||||
},
|
||||
"POWERED_BY": "Chatwoot AI",
|
||||
"DISMISS": "Descartar",
|
||||
"ADD_SELECTED_LABELS": "Add selected labels",
|
||||
"ADD_SELECTED_LABEL": "Add selected label",
|
||||
"ADD_ALL_LABELS": "Add all labels"
|
||||
"ADD_SELECTED_LABELS": "Asignar etiquetas seleccionadas",
|
||||
"ADD_SELECTED_LABEL": "Añadir etiqueta seleccionada",
|
||||
"ADD_ALL_LABELS": "Añadir todas las etiquetas"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Añadir etiqueta",
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"INCOMING_MESSAGES": {
|
||||
"NAME": "Mensajes entrantes",
|
||||
"NAME": "Mensajes recibidos",
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"OUTGOING_MESSAGES": {
|
||||
"NAME": "Mensajes salientes",
|
||||
"NAME": "Mensajes enviados",
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"FIRST_RESPONSE_TIME": {
|
||||
@@ -36,8 +36,8 @@
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"REPLY_TIME": {
|
||||
"NAME": "Customer waiting time",
|
||||
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} conversations)"
|
||||
"NAME": "Tiempo de espera del cliente",
|
||||
"TOOLTIP_TEXT": "El tiempo de espera es %{metricValue} (basado en %{conversationCount} conversaciones)"
|
||||
}
|
||||
},
|
||||
"DATE_RANGE_OPTIONS": {
|
||||
@@ -117,10 +117,6 @@
|
||||
}
|
||||
],
|
||||
"GROUP_BY_YEAR_OPTIONS": [
|
||||
{
|
||||
"id": 1,
|
||||
"groupBy": "Día"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"groupBy": "Semana"
|
||||
@@ -128,6 +124,10 @@
|
||||
{
|
||||
"id": 3,
|
||||
"groupBy": "Mes"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"groupBy": "Mes"
|
||||
}
|
||||
],
|
||||
"BUSINESS_HOURS": "Horarios"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"RESET_PASSWORD": {
|
||||
"TITLE": "Restablecer contraseña",
|
||||
"DESCRIPTION": "Enter the email address you use to log in to Chatwoot to get the password reset instructions.",
|
||||
"GO_BACK_TO_LOGIN": "If you want to go back to the login page,",
|
||||
"DESCRIPTION": "Introduzca la dirección de correo electrónico que utiliza para iniciar sesión en Chatwoot para obtener las instrucciones de restablecimiento de contraseña.",
|
||||
"GO_BACK_TO_LOGIN": "Si desea volver a la página de inicio de sesión,",
|
||||
"EMAIL": {
|
||||
"LABEL": "E-mail",
|
||||
"PLACEHOLDER": "Por favor ingrese su email.",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
"NOTE": "Cree una firma de mensaje personal que se añadirá a todos los mensajes que envíe desde su buzón de correo electrónico. Utilice el rico editor de contenidos para crear una firma altamente personalizada.",
|
||||
"BTN_TEXT": "Guardar firma de mensaje",
|
||||
"API_ERROR": "¡No se pudo guardar la firma! Inténtalo de nuevo",
|
||||
"API_SUCCESS": "Firma guardada correctamente"
|
||||
"API_SUCCESS": "Firma guardada correctamente",
|
||||
"IMAGE_UPLOAD_ERROR": "¡No se pudo subir la imagen! Intente nuevamente",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Imagen agregada satisfactoriamente. Por favor haga clic en salvar para guardar la firma",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "El tamaño de la imagen debe ser menor que {size}MB"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Firma del mensaje",
|
||||
@@ -149,7 +152,7 @@
|
||||
"SELECTOR_SUBTITLE": "Seleccione una cuenta de la siguiente lista",
|
||||
"PROFILE_SETTINGS": "Ajustes del perfil",
|
||||
"KEYBOARD_SHORTCUTS": "Atajos de teclado",
|
||||
"APPEARANCE": "Change Appearance",
|
||||
"APPEARANCE": "Cambiar apariencia",
|
||||
"SUPER_ADMIN_CONSOLE": "Consola de Super Admin",
|
||||
"LOGOUT": "Cerrar sesión"
|
||||
},
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
"PLACEHOLDER": {
|
||||
"AGENT": "جستجوی اپراتور",
|
||||
"TEAM": "جستجوی تیم",
|
||||
"INPUT": "Search for agents"
|
||||
"INPUT": "جستجوی اپراتور"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user