Merge branch 'release/1.6.0'
This commit is contained in:
commit
d03fad3e1d
827 changed files with 26325 additions and 3033 deletions
|
@ -16,7 +16,7 @@ defaults: &defaults
|
||||||
- image: circleci/redis:alpine
|
- image: circleci/redis:alpine
|
||||||
environment:
|
environment:
|
||||||
- CC_TEST_REPORTER_ID: b1b5c4447bf93f6f0b06a64756e35afd0810ea83649f03971cbf303b4449456f
|
- CC_TEST_REPORTER_ID: b1b5c4447bf93f6f0b06a64756e35afd0810ea83649f03971cbf303b4449456f
|
||||||
|
- RAILS_LOG_TO_STDOUT: false
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
@ -69,11 +69,11 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Download cc-test-reporter
|
name: Download cc-test-reporter
|
||||||
command: |
|
command: |
|
||||||
mkdir -p tmp/
|
mkdir -p ~/tmp
|
||||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./tmp/cc-test-reporter
|
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ~/tmp/cc-test-reporter
|
||||||
chmod +x ./tmp/cc-test-reporter
|
chmod +x ~/tmp/cc-test-reporter
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: tmp
|
root: ~/tmp
|
||||||
paths:
|
paths:
|
||||||
- cc-test-reporter
|
- cc-test-reporter
|
||||||
|
|
||||||
|
@ -98,10 +98,10 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Run backend tests
|
name: Run backend tests
|
||||||
command: |
|
command: |
|
||||||
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
|
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) --profile=10
|
||||||
./tmp/cc-test-reporter format-coverage -t simplecov -o tmp/codeclimate.backend.json coverage/backend/.resultset.json
|
~/tmp/cc-test-reporter format-coverage -t simplecov -o ~/tmp/codeclimate.backend.json coverage/backend/.resultset.json
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: tmp
|
root: ~/tmp
|
||||||
paths:
|
paths:
|
||||||
- codeclimate.backend.json
|
- codeclimate.backend.json
|
||||||
|
|
||||||
|
@ -109,21 +109,23 @@ jobs:
|
||||||
name: Run frontend tests
|
name: Run frontend tests
|
||||||
command: |
|
command: |
|
||||||
yarn test:coverage
|
yarn test:coverage
|
||||||
./tmp/cc-test-reporter format-coverage -t lcov -o tmp/codeclimate.frontend.json buildreports/lcov.info
|
~/tmp/cc-test-reporter format-coverage -t lcov -o ~/tmp/codeclimate.frontend.json buildreports/lcov.info
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: tmp
|
root: ~/tmp
|
||||||
paths:
|
paths:
|
||||||
- codeclimate.frontend.json
|
- codeclimate.frontend.json
|
||||||
|
|
||||||
# collect reports
|
# collect reports
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: /tmp/test-results
|
path: ~/tmp/test-results
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/test-results
|
path: ~/tmp/test-results
|
||||||
destination: test-results
|
destination: test-results
|
||||||
|
- store_artifacts:
|
||||||
|
path: log
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Upload coverage results to Code Climate
|
name: Upload coverage results to Code Climate
|
||||||
command: |
|
command: |
|
||||||
./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json -p 2 -o tmp/codeclimate.total.json
|
~/tmp/cc-test-reporter sum-coverage ~/tmp/codeclimate.*.json -p 2 -o ~/tmp/codeclimate.total.json
|
||||||
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
|
~/tmp/cc-test-reporter upload-coverage -i ~/tmp/codeclimate.total.json
|
||||||
|
|
22
.env.example
22
.env.example
|
@ -59,6 +59,7 @@ MANDRILL_INGRESS_API_KEY=
|
||||||
ACTIVE_STORAGE_SERVICE=local
|
ACTIVE_STORAGE_SERVICE=local
|
||||||
|
|
||||||
# Amazon S3
|
# Amazon S3
|
||||||
|
# documentation: https://www.chatwoot.com/docs/configuring-s3-bucket-as-cloud-storage
|
||||||
S3_BUCKET_NAME=
|
S3_BUCKET_NAME=
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
AWS_SECRET_ACCESS_KEY=
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
@ -74,34 +75,35 @@ LOG_LEVEL=info
|
||||||
LOG_SIZE=500
|
LOG_SIZE=500
|
||||||
|
|
||||||
### This environment variables are only required if you are setting up social media channels
|
### This environment variables are only required if you are setting up social media channels
|
||||||
#facebook
|
|
||||||
|
# Facebook
|
||||||
|
# documentation: https://www.chatwoot.com/docs/facebook-setup
|
||||||
FB_VERIFY_TOKEN=
|
FB_VERIFY_TOKEN=
|
||||||
FB_APP_SECRET=
|
FB_APP_SECRET=
|
||||||
FB_APP_ID=
|
FB_APP_ID=
|
||||||
|
|
||||||
# Twitter
|
# Twitter
|
||||||
|
# documentation: https://www.chatwoot.com/docs/twitter-app-setup
|
||||||
TWITTER_APP_ID=
|
TWITTER_APP_ID=
|
||||||
TWITTER_CONSUMER_KEY=
|
TWITTER_CONSUMER_KEY=
|
||||||
TWITTER_CONSUMER_SECRET=
|
TWITTER_CONSUMER_SECRET=
|
||||||
TWITTER_ENVIRONMENT=
|
TWITTER_ENVIRONMENT=
|
||||||
|
|
||||||
|
#slack integration
|
||||||
|
SLACK_CLIENT_ID=
|
||||||
|
SLACK_CLIENT_SECRET=
|
||||||
|
|
||||||
### Change this env variable only if you are using a custom build mobile app
|
### Change this env variable only if you are using a custom build mobile app
|
||||||
## Mobile app env variables
|
## Mobile app env variables
|
||||||
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
|
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
|
||||||
|
|
||||||
#### This environment variables are only required in hosted version which has billing
|
|
||||||
ENABLE_BILLING=
|
|
||||||
|
|
||||||
## chargebee settings
|
|
||||||
CHARGEBEE_API_KEY=
|
|
||||||
CHARGEBEE_SITE=
|
|
||||||
CHARGEBEE_WEBHOOK_USERNAME=
|
|
||||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
|
||||||
|
|
||||||
## Push Notification
|
## Push Notification
|
||||||
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
||||||
# VAPID_PUBLIC_KEY=
|
# VAPID_PUBLIC_KEY=
|
||||||
# VAPID_PRIVATE_KEY=
|
# VAPID_PRIVATE_KEY=
|
||||||
|
#
|
||||||
|
# for mobile apps
|
||||||
|
# FCM_SERVER_KEY=
|
||||||
|
|
||||||
## Bot Customizations
|
## Bot Customizations
|
||||||
USE_INBOX_AVATAR_FOR_BOT=true
|
USE_INBOX_AVATAR_FOR_BOT=true
|
||||||
|
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +1,2 @@
|
||||||
open_collective: chatwoot
|
open_collective: chatwoot
|
||||||
|
github: chatwoot
|
||||||
|
|
22
.rubocop.yml
22
.rubocop.yml
|
@ -8,8 +8,17 @@ Lint/RaiseException:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Lint/StructNewOverride:
|
Lint/StructNewOverride:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
Lint/DeprecatedOpenSSLConstant:
|
||||||
|
Enabled: true
|
||||||
|
Lint/MixedRegexpCaptureTypes:
|
||||||
|
Enabled: true
|
||||||
Layout/LineLength:
|
Layout/LineLength:
|
||||||
Max: 150
|
Max: 150
|
||||||
|
Layout/EmptyLinesAroundAttributeAccessor:
|
||||||
|
Enabled: true
|
||||||
|
Layout/SpaceAroundMethodCallOperator:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 125
|
Max: 125
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -18,6 +27,8 @@ RSpec/ExampleLength:
|
||||||
Max: 25
|
Max: 25
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Style/ExponentialNotation:
|
||||||
|
Enabled: false
|
||||||
Style/FrozenStringLiteralComment:
|
Style/FrozenStringLiteralComment:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/SymbolArray:
|
Style/SymbolArray:
|
||||||
|
@ -28,6 +39,14 @@ Style/HashTransformKeys:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Style/HashTransformValues:
|
Style/HashTransformValues:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
Style/RedundantFetchBlock:
|
||||||
|
Enabled: true
|
||||||
|
Style/RedundantRegexpCharacterClass:
|
||||||
|
Enabled: true
|
||||||
|
Style/RedundantRegexpEscape:
|
||||||
|
Enabled: true
|
||||||
|
Style/SlicingWithRange:
|
||||||
|
Enabled: true
|
||||||
Style/GlobalVars:
|
Style/GlobalVars:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'config/initializers/redis.rb'
|
- 'config/initializers/redis.rb'
|
||||||
|
@ -65,7 +84,6 @@ Style/GuardClause:
|
||||||
- 'app/builders/account_builder.rb'
|
- 'app/builders/account_builder.rb'
|
||||||
- 'app/models/attachment.rb'
|
- 'app/models/attachment.rb'
|
||||||
- 'app/models/message.rb'
|
- 'app/models/message.rb'
|
||||||
- 'lib/webhooks/chargebee.rb'
|
|
||||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -103,8 +121,8 @@ AllCops:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'bin/**/*'
|
- 'bin/**/*'
|
||||||
- 'db/schema.rb'
|
- 'db/schema.rb'
|
||||||
- 'config/**/*'
|
|
||||||
- 'public/**/*'
|
- 'public/**/*'
|
||||||
|
- 'config/initializers/bot.rb'
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
- 'node_modules/**/*'
|
- 'node_modules/**/*'
|
||||||
- 'lib/tasks/auto_annotate_models.rake'
|
- 'lib/tasks/auto_annotate_models.rake'
|
||||||
|
|
|
@ -88,7 +88,6 @@ Naming/MemoizedInstanceVariableName:
|
||||||
- 'app/controllers/application_controller.rb'
|
- 'app/controllers/application_controller.rb'
|
||||||
- 'app/models/message.rb'
|
- 'app/models/message.rb'
|
||||||
- 'lib/integrations/widget/outgoing_message_builder.rb'
|
- 'lib/integrations/widget/outgoing_message_builder.rb'
|
||||||
- 'lib/webhooks/chargebee.rb'
|
|
||||||
|
|
||||||
# Offense count: 4
|
# Offense count: 4
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
|
@ -187,7 +186,6 @@ Rails/EnumHash:
|
||||||
- 'app/models/attachment.rb'
|
- 'app/models/attachment.rb'
|
||||||
- 'app/models/conversation.rb'
|
- 'app/models/conversation.rb'
|
||||||
- 'app/models/message.rb'
|
- 'app/models/message.rb'
|
||||||
- 'app/models/subscription.rb'
|
|
||||||
- 'app/models/user.rb'
|
- 'app/models/user.rb'
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
|
@ -226,7 +224,6 @@ Rails/Output:
|
||||||
Rails/TimeZone:
|
Rails/TimeZone:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/builders/report_builder.rb'
|
- 'app/builders/report_builder.rb'
|
||||||
- 'app/models/subscription.rb'
|
|
||||||
- 'lib/reports/update_account_identity.rb'
|
- 'lib/reports/update_account_identity.rb'
|
||||||
- 'lib/reports/update_agent_identity.rb'
|
- 'lib/reports/update_agent_identity.rb'
|
||||||
- 'lib/reports/update_identity.rb'
|
- 'lib/reports/update_identity.rb'
|
||||||
|
@ -269,24 +266,6 @@ Style/CommentedKeyword:
|
||||||
- 'app/controllers/api/v1/conversations/labels_controller.rb'
|
- 'app/controllers/api/v1/conversations/labels_controller.rb'
|
||||||
- 'app/controllers/api/v1/labels_controller.rb'
|
- 'app/controllers/api/v1/labels_controller.rb'
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Configuration parameters: EnforcedStyle.
|
|
||||||
# SupportedStyles: annotated, template, unannotated
|
|
||||||
Style/FormatStringToken:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/constants/redis_keys.rb'
|
|
||||||
|
|
||||||
# Offense count: 4
|
|
||||||
# Configuration parameters: AllowedVariables.
|
|
||||||
Style/GlobalVars:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/redis/alfred.rb'
|
|
||||||
|
|
||||||
# Offense count: 4
|
|
||||||
Style/IdenticalConditionalBranches:
|
|
||||||
Exclude:
|
|
||||||
- 'app/controllers/api/v1/reports_controller.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Configuration parameters: AllowIfModifier.
|
# Configuration parameters: AllowIfModifier.
|
||||||
Style/IfInsideElse:
|
Style/IfInsideElse:
|
||||||
|
|
|
@ -51,7 +51,7 @@ linters:
|
||||||
|
|
||||||
ElsePlacement:
|
ElsePlacement:
|
||||||
enabled: true
|
enabled: true
|
||||||
style: same_line # or 'new_line'
|
style: new_line
|
||||||
|
|
||||||
EmptyLineBetweenBlocks:
|
EmptyLineBetweenBlocks:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
7
Gemfile
7
Gemfile
|
@ -56,9 +56,6 @@ gem 'administrate'
|
||||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||||
gem 'wisper', '2.0.0'
|
gem 'wisper', '2.0.0'
|
||||||
|
|
||||||
##--- gems for billing ---##
|
|
||||||
gem 'chargebee'
|
|
||||||
|
|
||||||
##--- gems for channels ---##
|
##--- gems for channels ---##
|
||||||
gem 'facebook-messenger'
|
gem 'facebook-messenger'
|
||||||
gem 'telegram-bot-ruby'
|
gem 'telegram-bot-ruby'
|
||||||
|
@ -68,6 +65,8 @@ gem 'twilio-ruby', '~> 5.32.0'
|
||||||
gem 'twitty'
|
gem 'twitty'
|
||||||
# facebook client
|
# facebook client
|
||||||
gem 'koala'
|
gem 'koala'
|
||||||
|
# slack client
|
||||||
|
gem 'slack-ruby-client'
|
||||||
# Random name generator
|
# Random name generator
|
||||||
gem 'haikunator'
|
gem 'haikunator'
|
||||||
|
|
||||||
|
@ -84,6 +83,7 @@ gem 'sidekiq'
|
||||||
gem 'flag_shih_tzu'
|
gem 'flag_shih_tzu'
|
||||||
|
|
||||||
##-- Push notification service --##
|
##-- Push notification service --##
|
||||||
|
gem 'fcm'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
@ -117,4 +117,5 @@ group :development, :test do
|
||||||
gem 'simplecov', '0.17.1', require: false
|
gem 'simplecov', '0.17.1', require: false
|
||||||
gem 'spring'
|
gem 'spring'
|
||||||
gem 'spring-watcher-listen'
|
gem 'spring-watcher-listen'
|
||||||
|
gem 'webmock'
|
||||||
end
|
end
|
||||||
|
|
240
Gemfile.lock
240
Gemfile.lock
|
@ -18,56 +18,56 @@ GEM
|
||||||
specs:
|
specs:
|
||||||
action-cable-testing (0.6.1)
|
action-cable-testing (0.6.1)
|
||||||
actioncable (>= 5.0)
|
actioncable (>= 5.0)
|
||||||
actioncable (6.0.3.1)
|
actioncable (6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.0.3.1)
|
actionmailbox (6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
activejob (= 6.0.3.1)
|
activejob (= 6.0.3.2)
|
||||||
activerecord (= 6.0.3.1)
|
activerecord (= 6.0.3.2)
|
||||||
activestorage (= 6.0.3.1)
|
activestorage (= 6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.0.3.1)
|
actionmailer (6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
actionview (= 6.0.3.1)
|
actionview (= 6.0.3.2)
|
||||||
activejob (= 6.0.3.1)
|
activejob (= 6.0.3.2)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.0.3.1)
|
actionpack (6.0.3.2)
|
||||||
actionview (= 6.0.3.1)
|
actionview (= 6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
rack (~> 2.0, >= 2.0.8)
|
rack (~> 2.0, >= 2.0.8)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.0.3.1)
|
actiontext (6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
activerecord (= 6.0.3.1)
|
activerecord (= 6.0.3.2)
|
||||||
activestorage (= 6.0.3.1)
|
activestorage (= 6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.0.3.1)
|
actionview (6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activejob (6.0.3.1)
|
activejob (6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.0.3.1)
|
activemodel (6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
activerecord (6.0.3.1)
|
activerecord (6.0.3.2)
|
||||||
activemodel (= 6.0.3.1)
|
activemodel (= 6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
activestorage (6.0.3.1)
|
activestorage (6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
activejob (= 6.0.3.1)
|
activejob (= 6.0.3.2)
|
||||||
activerecord (= 6.0.3.1)
|
activerecord (= 6.0.3.2)
|
||||||
marcel (~> 0.3.1)
|
marcel (~> 0.3.1)
|
||||||
activesupport (6.0.3.1)
|
activesupport (6.0.3.2)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
|
@ -91,26 +91,26 @@ GEM
|
||||||
annotate (3.1.1)
|
annotate (3.1.1)
|
||||||
activerecord (>= 3.2, < 7.0)
|
activerecord (>= 3.2, < 7.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
ast (2.4.0)
|
ast (2.4.1)
|
||||||
attr_extras (6.2.3)
|
attr_extras (6.2.4)
|
||||||
autoprefixer-rails (9.7.6)
|
autoprefixer-rails (9.8.2)
|
||||||
execjs
|
execjs
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.317.0)
|
aws-partitions (1.332.0)
|
||||||
aws-sdk-core (3.96.1)
|
aws-sdk-core (3.100.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.31.0)
|
aws-sdk-kms (1.34.1)
|
||||||
aws-sdk-core (~> 3, >= 3.71.0)
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.65.0)
|
aws-sdk-s3 (1.69.1)
|
||||||
aws-sdk-core (~> 3, >= 3.96.1)
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.1.3)
|
aws-sigv4 (1.2.0)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
axiom-types (0.1.1)
|
axiom-types (0.1.1)
|
||||||
descendants_tracker (~> 0.0.4)
|
descendants_tracker (~> 0.0.4)
|
||||||
ice_nine (~> 0.11.0)
|
ice_nine (~> 0.11.0)
|
||||||
|
@ -127,25 +127,24 @@ GEM
|
||||||
bootsnap (1.4.6)
|
bootsnap (1.4.6)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.8.2)
|
brakeman (4.8.2)
|
||||||
browser (4.1.0)
|
browser (4.2.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (6.1.0)
|
bullet (6.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
bundle-audit (0.1.0)
|
bundle-audit (0.1.0)
|
||||||
bundler-audit
|
bundler-audit
|
||||||
bundler-audit (0.6.1)
|
bundler-audit (0.7.0.1)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 0.18)
|
thor (>= 0.18, < 2)
|
||||||
byebug (11.1.3)
|
byebug (11.1.3)
|
||||||
chargebee (2.7.5)
|
coderay (1.1.3)
|
||||||
json_pure (~> 2.1)
|
|
||||||
rest-client (>= 1.8, < 3.0)
|
|
||||||
coderay (1.1.2)
|
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
descendants_tracker (~> 0.0.1)
|
descendants_tracker (~> 0.0.1)
|
||||||
concurrent-ruby (1.1.6)
|
concurrent-ruby (1.1.6)
|
||||||
connection_pool (2.2.2)
|
connection_pool (2.2.3)
|
||||||
|
crack (0.4.3)
|
||||||
|
safe_yaml (~> 1.0.0)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
datetime_picker_rails (0.0.7)
|
datetime_picker_rails (0.0.7)
|
||||||
momentjs-rails (>= 2.8.1)
|
momentjs-rails (>= 2.8.1)
|
||||||
|
@ -153,17 +152,18 @@ GEM
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
descendants_tracker (0.0.4)
|
descendants_tracker (0.0.4)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
devise (4.7.1)
|
devise (4.7.2)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise_token_auth (1.1.3)
|
devise_token_auth (1.1.4)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
devise (> 3.5.2, < 5)
|
devise (> 3.5.2, < 5)
|
||||||
rails (>= 4.2.0, < 6.1)
|
rails (>= 4.2.0, < 6.1)
|
||||||
diff-lcs (1.3)
|
sprockets (= 3.7.2)
|
||||||
|
diff-lcs (1.4)
|
||||||
digest-crc (0.5.1)
|
digest-crc (0.5.1)
|
||||||
docile (1.3.2)
|
docile (1.3.2)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
|
@ -178,23 +178,26 @@ GEM
|
||||||
facebook-messenger (1.5.0)
|
facebook-messenger (1.5.0)
|
||||||
httparty (~> 0.13, >= 0.13.7)
|
httparty (~> 0.13, >= 0.13.7)
|
||||||
rack (>= 1.4.5)
|
rack (>= 1.4.5)
|
||||||
factory_bot (5.2.0)
|
factory_bot (6.0.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (5.2.0)
|
factory_bot_rails (6.0.0)
|
||||||
factory_bot (~> 5.2.0)
|
factory_bot (~> 6.0.0)
|
||||||
railties (>= 4.2.0)
|
railties (>= 5.0.0)
|
||||||
faker (2.11.0)
|
faker (2.12.0)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
faraday (1.0.1)
|
faraday (1.0.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
ffi (1.12.2)
|
fcm (1.0.1)
|
||||||
|
faraday (~> 1.0.0)
|
||||||
|
ffi (1.13.1)
|
||||||
flag_shih_tzu (0.3.23)
|
flag_shih_tzu (0.3.23)
|
||||||
foreman (0.87.1)
|
foreman (0.87.1)
|
||||||
|
gli (2.19.1)
|
||||||
globalid (0.4.2)
|
globalid (0.4.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
google-api-client (0.39.4)
|
google-api-client (0.41.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
|
@ -205,17 +208,17 @@ GEM
|
||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.1)
|
google-cloud-env (1.3.2)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.0)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.26.1)
|
google-cloud-storage (1.26.2)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-api-client (~> 0.33)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.12.0)
|
googleauth (0.13.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
|
@ -226,16 +229,17 @@ GEM
|
||||||
activesupport (>= 5)
|
activesupport (>= 5)
|
||||||
haikunator (1.1.0)
|
haikunator (1.1.0)
|
||||||
hana (1.3.6)
|
hana (1.3.6)
|
||||||
|
hashdiff (1.0.1)
|
||||||
hashie (4.1.0)
|
hashie (4.1.0)
|
||||||
hkdf (0.3.0)
|
hkdf (0.3.0)
|
||||||
http-accept (1.7.0)
|
http-accept (1.7.0)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httparty (0.18.0)
|
httparty (0.18.1)
|
||||||
mime-types (~> 3.0)
|
mime-types (~> 3.0)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.8.2)
|
i18n (1.8.3)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
ice_nine (0.11.2)
|
ice_nine (0.11.2)
|
||||||
inflecto (0.0.2)
|
inflecto (0.0.2)
|
||||||
|
@ -247,7 +251,6 @@ GEM
|
||||||
railties (>= 4.2.0)
|
railties (>= 4.2.0)
|
||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
json_pure (2.3.0)
|
|
||||||
jwt (2.2.1)
|
jwt (2.2.1)
|
||||||
kaminari (1.2.1)
|
kaminari (1.2.1)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
|
@ -272,7 +275,7 @@ GEM
|
||||||
listen (3.2.1)
|
listen (3.2.1)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
loofah (2.5.0)
|
loofah (2.6.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.7.1)
|
mail (2.7.1)
|
||||||
|
@ -302,9 +305,9 @@ GEM
|
||||||
oauth (0.5.4)
|
oauth (0.5.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.0)
|
os (1.1.0)
|
||||||
parallel (1.19.1)
|
parallel (1.19.2)
|
||||||
parser (2.7.1.2)
|
parser (2.7.1.4)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.1)
|
||||||
pg (1.2.3)
|
pg (1.2.3)
|
||||||
pry (0.13.1)
|
pry (0.13.1)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
|
@ -316,8 +319,8 @@ GEM
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.1.0)
|
pundit (2.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
rack (2.2.2)
|
rack (2.2.3)
|
||||||
rack-cache (1.11.1)
|
rack-cache (1.12.0)
|
||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
|
@ -327,29 +330,29 @@ GEM
|
||||||
rack
|
rack
|
||||||
rack-test (1.1.0)
|
rack-test (1.1.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rails (6.0.3.1)
|
rails (6.0.3.2)
|
||||||
actioncable (= 6.0.3.1)
|
actioncable (= 6.0.3.2)
|
||||||
actionmailbox (= 6.0.3.1)
|
actionmailbox (= 6.0.3.2)
|
||||||
actionmailer (= 6.0.3.1)
|
actionmailer (= 6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
actiontext (= 6.0.3.1)
|
actiontext (= 6.0.3.2)
|
||||||
actionview (= 6.0.3.1)
|
actionview (= 6.0.3.2)
|
||||||
activejob (= 6.0.3.1)
|
activejob (= 6.0.3.2)
|
||||||
activemodel (= 6.0.3.1)
|
activemodel (= 6.0.3.2)
|
||||||
activerecord (= 6.0.3.1)
|
activerecord (= 6.0.3.2)
|
||||||
activestorage (= 6.0.3.1)
|
activestorage (= 6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
bundler (>= 1.3.0)
|
bundler (>= 1.3.0)
|
||||||
railties (= 6.0.3.1)
|
railties (= 6.0.3.2)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.3.0)
|
rails-html-sanitizer (1.3.0)
|
||||||
loofah (~> 2.3)
|
loofah (~> 2.3)
|
||||||
railties (6.0.3.1)
|
railties (6.0.3.2)
|
||||||
actionpack (= 6.0.3.1)
|
actionpack (= 6.0.3.2)
|
||||||
activesupport (= 6.0.3.1)
|
activesupport (= 6.0.3.2)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.20.3, < 2.0)
|
thor (>= 0.20.3, < 2.0)
|
||||||
|
@ -358,19 +361,20 @@ GEM
|
||||||
rb-fsevent (0.10.4)
|
rb-fsevent (0.10.4)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
redis (4.1.4)
|
redis (4.2.1)
|
||||||
redis-namespace (1.7.0)
|
redis-namespace (1.7.0)
|
||||||
redis (>= 3.0.4)
|
redis (>= 3.0.4)
|
||||||
redis-rack-cache (2.2.1)
|
redis-rack-cache (2.2.1)
|
||||||
rack-cache (>= 1.10, < 2)
|
rack-cache (>= 1.10, < 2)
|
||||||
redis-store (>= 1.6, < 2)
|
redis-store (>= 1.6, < 2)
|
||||||
redis-store (1.8.2)
|
redis-store (1.9.0)
|
||||||
redis (>= 4, < 5)
|
redis (>= 4, < 5)
|
||||||
|
regexp_parser (1.7.1)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
responders (3.0.0)
|
responders (3.0.1)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
rest-client (2.1.0)
|
rest-client (2.1.0)
|
||||||
|
@ -397,28 +401,33 @@ GEM
|
||||||
rspec-mocks (~> 3.9)
|
rspec-mocks (~> 3.9)
|
||||||
rspec-support (~> 3.9)
|
rspec-support (~> 3.9)
|
||||||
rspec-support (3.9.3)
|
rspec-support (3.9.3)
|
||||||
rubocop (0.83.0)
|
rubocop (0.86.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.7.0.1)
|
parser (>= 2.7.0.1)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
regexp_parser (>= 1.7)
|
||||||
rexml
|
rexml
|
||||||
|
rubocop-ast (>= 0.0.3, < 1.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 2.0)
|
unicode-display_width (>= 1.4.0, < 2.0)
|
||||||
rubocop-performance (1.5.2)
|
rubocop-ast (0.0.3)
|
||||||
|
parser (>= 2.7.0.1)
|
||||||
|
rubocop-performance (1.6.1)
|
||||||
rubocop (>= 0.71.0)
|
rubocop (>= 0.71.0)
|
||||||
rubocop-rails (2.5.2)
|
rubocop-rails (2.6.0)
|
||||||
activesupport
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 0.72.0)
|
rubocop (>= 0.82.0)
|
||||||
rubocop-rspec (1.39.0)
|
rubocop-rspec (1.40.0)
|
||||||
rubocop (>= 0.68.1)
|
rubocop (>= 0.68.1)
|
||||||
ruby-progressbar (1.10.1)
|
ruby-progressbar (1.10.1)
|
||||||
|
safe_yaml (1.0.5)
|
||||||
sass (3.7.4)
|
sass (3.7.4)
|
||||||
sass-listen (~> 4.0.0)
|
sass-listen (~> 4.0.0)
|
||||||
sass-listen (4.0.0)
|
sass-listen (4.0.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
sassc (2.3.0)
|
sassc (2.4.0)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
sassc-rails (2.1.2)
|
sassc-rails (2.1.2)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
|
@ -454,11 +463,18 @@ GEM
|
||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.2)
|
simplecov-html (0.10.2)
|
||||||
|
slack-ruby-client (0.14.6)
|
||||||
|
activesupport
|
||||||
|
faraday (>= 0.9)
|
||||||
|
faraday_middleware
|
||||||
|
gli
|
||||||
|
hashie
|
||||||
|
websocket-driver
|
||||||
spring (2.1.0)
|
spring (2.1.0)
|
||||||
spring-watcher-listen (2.0.1)
|
spring-watcher-listen (2.0.1)
|
||||||
listen (>= 2.7, < 4.0)
|
listen (>= 2.7, < 4.0)
|
||||||
spring (>= 1.2, < 3.0)
|
spring (>= 1.2, < 3.0)
|
||||||
sprockets (4.0.0)
|
sprockets (3.7.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
sprockets-rails (3.2.1)
|
sprockets-rails (3.2.1)
|
||||||
|
@ -470,7 +486,7 @@ GEM
|
||||||
inflecto
|
inflecto
|
||||||
virtus
|
virtus
|
||||||
telephone_number (1.4.7)
|
telephone_number (1.4.7)
|
||||||
thor (0.20.3)
|
thor (1.0.1)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.10)
|
tilt (2.0.10)
|
||||||
time_diff (0.3.0)
|
time_diff (0.3.0)
|
||||||
|
@ -504,11 +520,15 @@ GEM
|
||||||
equalizer (~> 0.0, >= 0.0.9)
|
equalizer (~> 0.0, >= 0.0.9)
|
||||||
warden (1.2.8)
|
warden (1.2.8)
|
||||||
rack (>= 2.0.6)
|
rack (>= 2.0.6)
|
||||||
web-console (4.0.2)
|
web-console (4.0.3)
|
||||||
actionview (>= 6.0.0)
|
actionview (>= 6.0.0)
|
||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
webmock (3.8.3)
|
||||||
|
addressable (>= 2.3.6)
|
||||||
|
crack (>= 0.3.2)
|
||||||
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webpacker (5.1.1)
|
webpacker (5.1.1)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
|
@ -517,9 +537,9 @@ GEM
|
||||||
webpush (1.0.0)
|
webpush (1.0.0)
|
||||||
hkdf (~> 0.2)
|
hkdf (~> 0.2)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
websocket-driver (0.7.1)
|
websocket-driver (0.7.2)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.4)
|
websocket-extensions (0.1.5)
|
||||||
wisper (2.0.0)
|
wisper (2.0.0)
|
||||||
zeitwerk (2.3.0)
|
zeitwerk (2.3.0)
|
||||||
|
|
||||||
|
@ -540,13 +560,13 @@ DEPENDENCIES
|
||||||
bullet
|
bullet
|
||||||
bundle-audit
|
bundle-audit
|
||||||
byebug
|
byebug
|
||||||
chargebee
|
|
||||||
devise
|
devise
|
||||||
devise_token_auth
|
devise_token_auth
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
facebook-messenger
|
facebook-messenger
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
faker
|
faker
|
||||||
|
fcm
|
||||||
flag_shih_tzu
|
flag_shih_tzu
|
||||||
foreman
|
foreman
|
||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
|
@ -585,6 +605,7 @@ DEPENDENCIES
|
||||||
shoulda-matchers
|
shoulda-matchers
|
||||||
sidekiq
|
sidekiq
|
||||||
simplecov (= 0.17.1)
|
simplecov (= 0.17.1)
|
||||||
|
slack-ruby-client
|
||||||
spring
|
spring
|
||||||
spring-watcher-listen
|
spring-watcher-listen
|
||||||
telegram-bot-ruby
|
telegram-bot-ruby
|
||||||
|
@ -596,6 +617,7 @@ DEPENDENCIES
|
||||||
uglifier
|
uglifier
|
||||||
valid_email2
|
valid_email2
|
||||||
web-console
|
web-console
|
||||||
|
webmock
|
||||||
webpacker
|
webpacker
|
||||||
webpush
|
webpush
|
||||||
wisper (= 2.0.0)
|
wisper (= 2.0.0)
|
||||||
|
|
|
@ -29,7 +29,7 @@ class ContactMergeAction
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_messages
|
def merge_messages
|
||||||
Message.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
|
Message.where(sender: @mergee_contact).update(sender: @base_contact)
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_contact_inboxes
|
def merge_contact_inboxes
|
||||||
|
|
|
@ -117,7 +117,8 @@ class Messages::MessageBuilder
|
||||||
inbox_id: conversation.inbox_id,
|
inbox_id: conversation.inbox_id,
|
||||||
message_type: @message_type,
|
message_type: @message_type,
|
||||||
content: response.content,
|
content: response.content,
|
||||||
source_id: response.identifier
|
source_id: response.identifier,
|
||||||
|
sender: contact
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ class Messages::Outgoing::NormalBuilder
|
||||||
message_type: :outgoing,
|
message_type: :outgoing,
|
||||||
content: @content,
|
content: @content,
|
||||||
private: @private,
|
private: @private,
|
||||||
user_id: @user&.id,
|
sender: @user,
|
||||||
source_id: @fb_id,
|
source_id: @fb_id,
|
||||||
content_type: @content_type,
|
content_type: @content_type,
|
||||||
items: @items
|
items: @items
|
||||||
|
|
|
@ -12,6 +12,8 @@ class NotificationSubscriptionBuilder
|
||||||
|
|
||||||
def identifier
|
def identifier
|
||||||
@identifier ||= params[:subscription_attributes][:endpoint] if params[:subscription_type] == 'browser_push'
|
@identifier ||= params[:subscription_attributes][:endpoint] if params[:subscription_type] == 'browser_push'
|
||||||
|
@identifier ||= params[:subscription_attributes][:device_id] if params[:subscription_type] == 'fcm'
|
||||||
|
@identifier
|
||||||
end
|
end
|
||||||
|
|
||||||
def identifier_subscription
|
def identifier_subscription
|
||||||
|
|
|
@ -1,10 +1,47 @@
|
||||||
class RoomChannel < ApplicationCable::Channel
|
class RoomChannel < ApplicationCable::Channel
|
||||||
def subscribed
|
def subscribed
|
||||||
stream_from params[:pubsub_token]
|
ensure_stream
|
||||||
::OnlineStatusTracker.add_subscription(params[:pubsub_token])
|
current_user
|
||||||
|
current_account
|
||||||
|
update_subscription
|
||||||
|
broadcast_presence
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsubscribed
|
def update_presence
|
||||||
::OnlineStatusTracker.remove_subscription(params[:pubsub_token])
|
update_subscription
|
||||||
|
broadcast_presence
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def broadcast_presence
|
||||||
|
data = { account_id: @current_account.id, users: ::OnlineStatusTracker.get_available_users(@current_account.id) }
|
||||||
|
data[:contacts] = ::OnlineStatusTracker.get_available_contacts(@current_account.id) if @current_user.is_a? User
|
||||||
|
ActionCable.server.broadcast(@pubsub_token, { event: 'presence.update', data: data })
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_stream
|
||||||
|
@pubsub_token = params[:pubsub_token]
|
||||||
|
stream_from @pubsub_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_subscription
|
||||||
|
::OnlineStatusTracker.update_presence(@current_account.id, @current_user.class.name, @current_user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user
|
||||||
|
@current_user ||= if params[:user_id].blank?
|
||||||
|
Contact.find_by!(pubsub_token: @pubsub_token)
|
||||||
|
else
|
||||||
|
User.find_by!(pubsub_token: @pubsub_token, id: params[:user_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_account
|
||||||
|
@current_account ||= if @current_user.is_a? Contact
|
||||||
|
@current_user.account
|
||||||
|
else
|
||||||
|
@current_user.accounts.find(params[:account_id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,12 +10,4 @@ class Api::BaseController < ApplicationController
|
||||||
def authenticate_by_access_token?
|
def authenticate_by_access_token?
|
||||||
request.headers[:api_access_token].present? || request.headers[:HTTP_API_ACCESS_TOKEN].present?
|
request.headers[:api_access_token].present? || request.headers[:HTTP_API_ACCESS_TOKEN].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_conversation
|
|
||||||
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_billing_enabled
|
|
||||||
raise ActionController::RoutingError, 'Not Found' unless ENV['BILLING_ENABLED']
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
|
class Api::V1::Accounts::Actions::ContactMergesController < Api::V1::Accounts::BaseController
|
||||||
before_action :set_base_contact, only: [:create]
|
before_action :set_base_contact, only: [:create]
|
||||||
before_action :set_mergee_contact, only: [:create]
|
before_action :set_mergee_contact, only: [:create]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
contact_merge_action = ContactMergeAction.new(
|
contact_merge_action = ContactMergeAction.new(
|
||||||
account: current_account,
|
account: Current.account,
|
||||||
base_contact: @base_contact,
|
base_contact: @base_contact,
|
||||||
mergee_contact: @mergee_contact
|
mergee_contact: @mergee_contact
|
||||||
)
|
)
|
||||||
|
@ -23,6 +23,6 @@ class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def contacts
|
def contacts
|
||||||
@contacts ||= current_account.contacts
|
@contacts ||= Current.account.contacts
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::Accounts::AgentsController < Api::BaseController
|
class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
|
||||||
before_action :fetch_agent, except: [:create, :index]
|
before_action :fetch_agent, except: [:create, :index]
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :find_user, only: [:create]
|
before_action :find_user, only: [:create]
|
||||||
|
@ -46,7 +46,7 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||||
|
|
||||||
def save_account_user
|
def save_account_user
|
||||||
AccountUser.create!(
|
AccountUser.create!(
|
||||||
account_id: current_account.id,
|
account_id: Current.account.id,
|
||||||
user_id: @user.id,
|
user_id: @user.id,
|
||||||
role: new_agent_params[:role],
|
role: new_agent_params[:role],
|
||||||
inviter_id: current_user.id
|
inviter_id: current_user.id
|
||||||
|
@ -64,6 +64,6 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def agents
|
def agents
|
||||||
@agents ||= current_account.users
|
@agents ||= Current.account.users
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
31
app/controllers/api/v1/accounts/base_controller.rb
Normal file
31
app/controllers/api/v1/accounts/base_controller.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
class Api::V1::Accounts::BaseController < Api::BaseController
|
||||||
|
before_action :current_account
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def current_account
|
||||||
|
@current_account ||= ensure_current_account
|
||||||
|
Current.account = @current_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_current_account
|
||||||
|
account = Account.find(params[:account_id])
|
||||||
|
if current_user
|
||||||
|
account_accessible_for_user?(account)
|
||||||
|
elsif @resource&.is_a?(AgentBot)
|
||||||
|
account_accessible_for_bot?(account)
|
||||||
|
end
|
||||||
|
switch_locale account
|
||||||
|
account
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_accessible_for_user?(account)
|
||||||
|
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
||||||
|
Current.account_user = @current_account_user
|
||||||
|
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::Accounts::CallbacksController < Api::BaseController
|
class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
|
||||||
before_action :inbox, only: [:reauthorize_page]
|
before_action :inbox, only: [:reauthorize_page]
|
||||||
|
|
||||||
def register_facebook_page
|
def register_facebook_page
|
||||||
|
@ -7,11 +7,11 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||||
page_id = params[:page_id]
|
page_id = params[:page_id]
|
||||||
inbox_name = params[:inbox_name]
|
inbox_name = params[:inbox_name]
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
facebook_channel = current_account.facebook_pages.create!(
|
facebook_channel = Current.account.facebook_pages.create!(
|
||||||
page_id: page_id, user_access_token: user_access_token,
|
page_id: page_id, user_access_token: user_access_token,
|
||||||
page_access_token: page_access_token
|
page_access_token: page_access_token
|
||||||
)
|
)
|
||||||
@facebook_inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
@facebook_inbox = Current.account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||||
set_avatar(@facebook_inbox, page_id)
|
set_avatar(@facebook_inbox, page_id)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.info e
|
Rails.logger.info e
|
||||||
|
@ -22,7 +22,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||||
@page_details = mark_already_existing_facebook_pages(fb_object.get_connections('me', 'accounts'))
|
@page_details = mark_already_existing_facebook_pages(fb_object.get_connections('me', 'accounts'))
|
||||||
end
|
end
|
||||||
|
|
||||||
# get params[:inbox_id], current_account, params[:omniauth_token]
|
# get params[:inbox_id], current_account. params[:omniauth_token]
|
||||||
def reauthorize_page
|
def reauthorize_page
|
||||||
if @inbox&.facebook?
|
if @inbox&.facebook?
|
||||||
fb_page_id = @inbox.channel.page_id
|
fb_page_id = @inbox.channel.page_id
|
||||||
|
@ -40,7 +40,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def inbox
|
def inbox
|
||||||
@inbox = current_account.inboxes.find_by(id: params[:inbox_id])
|
@inbox = Current.account.inboxes.find_by(id: params[:inbox_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_fb_page(fb_page_id, access_token)
|
def update_fb_page(fb_page_id, access_token)
|
||||||
|
@ -50,7 +50,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_fb_page(fb_page_id)
|
def get_fb_page(fb_page_id)
|
||||||
current_account.facebook_pages.find_by(page_id: fb_page_id)
|
Current.account.facebook_pages.find_by(page_id: fb_page_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fb_object
|
def fb_object
|
||||||
|
@ -69,7 +69,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||||
return [] if data.empty?
|
return [] if data.empty?
|
||||||
|
|
||||||
data.inject([]) do |result, page_detail|
|
data.inject([]) do |result, page_detail|
|
||||||
page_detail[:exists] = current_account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
page_detail[:exists] = Current.account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
||||||
result << page_detail
|
result << page_detail
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
class Api::V1::Accounts::CannedResponsesController < Api::V1::Accounts::BaseController
|
||||||
before_action :fetch_canned_response, only: [:update, :destroy]
|
before_action :fetch_canned_response, only: [:update, :destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -6,7 +6,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@canned_response = current_account.canned_responses.new(canned_response_params)
|
@canned_response = Current.account.canned_responses.new(canned_response_params)
|
||||||
@canned_response.save!
|
@canned_response.save!
|
||||||
render json: @canned_response
|
render json: @canned_response
|
||||||
end
|
end
|
||||||
|
@ -24,7 +24,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def fetch_canned_response
|
def fetch_canned_response
|
||||||
@canned_response = current_account.canned_responses.find(params[:id])
|
@canned_response = Current.account.canned_responses.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def canned_response_params
|
def canned_response_params
|
||||||
|
@ -33,9 +33,9 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||||
|
|
||||||
def canned_responses
|
def canned_responses
|
||||||
if params[:search]
|
if params[:search]
|
||||||
current_account.canned_responses.where('short_code ILIKE ?', "#{params[:search]}%")
|
Current.account.canned_responses.where('short_code ILIKE ?', "#{params[:search]}%")
|
||||||
else
|
else
|
||||||
current_account.canned_responses
|
Current.account.canned_responses
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController
|
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts::BaseController
|
||||||
before_action :current_account
|
|
||||||
before_action :authorize_request
|
before_action :authorize_request
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -38,13 +37,13 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseControlle
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_inbox
|
def build_inbox
|
||||||
@twilio_channel = current_account.twilio_sms.create!(
|
@twilio_channel = Current.account.twilio_sms.create!(
|
||||||
account_sid: permitted_params[:account_sid],
|
account_sid: permitted_params[:account_sid],
|
||||||
auth_token: permitted_params[:auth_token],
|
auth_token: permitted_params[:auth_token],
|
||||||
phone_number: phone_number,
|
phone_number: phone_number,
|
||||||
medium: medium
|
medium: medium
|
||||||
)
|
)
|
||||||
@inbox = current_account.inboxes.create(
|
@inbox = Current.account.inboxes.create(
|
||||||
name: permitted_params[:name],
|
name: permitted_params[:name],
|
||||||
channel: @twilio_channel
|
channel: @twilio_channel
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController
|
class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::BaseController
|
||||||
def index
|
def index
|
||||||
@conversations = current_account.conversations.includes(
|
@conversations = Current.account.conversations.includes(
|
||||||
:assignee, :contact, :inbox
|
:assignee, :contact, :inbox
|
||||||
).where(inbox_id: inbox_ids, contact_id: permitted_params[:contact_id])
|
).where(inbox_id: inbox_ids, contact_id: permitted_params[:contact_id])
|
||||||
end
|
end
|
||||||
|
@ -9,7 +9,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController
|
||||||
|
|
||||||
def inbox_ids
|
def inbox_ids
|
||||||
if current_user.administrator?
|
if current_user.administrator?
|
||||||
current_account.inboxes.pluck(:id)
|
Current.account.inboxes.pluck(:id)
|
||||||
elsif current_user.agent?
|
elsif current_user.agent?
|
||||||
current_user.assigned_inboxes.pluck(:id)
|
current_user.assigned_inboxes.pluck(:id)
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
class Api::V1::Accounts::ContactsController < Api::BaseController
|
class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :fetch_contact, only: [:show, :update]
|
before_action :fetch_contact, only: [:show, :update]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@contacts = current_account.contacts
|
@contacts = Current.account.contacts
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@contact = Contact.new(contact_create_params)
|
@contact = Current.account.contacts.new(contact_create_params)
|
||||||
@contact.save!
|
@contact.save!
|
||||||
render json: @contact
|
render json: @contact
|
||||||
end
|
end
|
||||||
|
@ -31,10 +31,10 @@ class Api::V1::Accounts::ContactsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_contact
|
def fetch_contact
|
||||||
@contact = current_account.contacts.find(params[:id])
|
@contact = Current.account.contacts.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_create_params
|
def contact_create_params
|
||||||
params.require(:contact).permit(:account_id, :inbox_id).merge!(name: SecureRandom.hex)
|
params.require(:contact).permit(:name, :email, :phone_number)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::BaseController
|
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||||
before_action :set_conversation, only: [:create]
|
|
||||||
|
|
||||||
# assign agent to a conversation
|
# assign agent to a conversation
|
||||||
def create
|
def create
|
||||||
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
|
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
|
||||||
assignee = current_account.users.find_by(id: params[:assignee_id])
|
assignee = Current.account.users.find_by(id: params[:assignee_id])
|
||||||
@conversation.update_assignee(assignee)
|
@conversation.update_assignee(assignee)
|
||||||
render json: assignee
|
render json: assignee
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :conversation
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def conversation
|
||||||
|
@conversation ||= Current.account.conversations.find_by(display_id: params[:conversation_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,4 @@
|
||||||
class Api::V1::Accounts::Conversations::LabelsController < Api::BaseController
|
class Api::V1::Accounts::Conversations::LabelsController < Api::V1::Accounts::Conversations::BaseController
|
||||||
before_action :set_conversation, only: [:create, :index]
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@conversation.update_labels(params[:labels])
|
@conversation.update_labels(params[:labels])
|
||||||
@labels = @conversation.label_list
|
@labels = @conversation.label_list
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
class Api::V1::Accounts::Conversations::MessagesController < Api::BaseController
|
class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
|
||||||
before_action :set_conversation, only: [:index, :create]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@messages = message_finder.perform
|
@messages = message_finder.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
|
user = current_user || @resource
|
||||||
|
mb = Messages::Outgoing::NormalBuilder.new(user, @conversation, params)
|
||||||
@message = mb.perform
|
@message = mb.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
class Api::V1::Accounts::ConversationsController < Api::BaseController
|
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
||||||
include Events::Types
|
include Events::Types
|
||||||
before_action :current_account
|
|
||||||
before_action :conversation, except: [:index]
|
before_action :conversation, except: [:index]
|
||||||
before_action :contact_inbox, only: [:create]
|
before_action :contact_inbox, only: [:create]
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
|
@conversation ||= Current.account.conversations.find_by(display_id: params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_inbox
|
def contact_inbox
|
||||||
|
@ -71,7 +70,7 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||||
|
|
||||||
def conversation_params
|
def conversation_params
|
||||||
{
|
{
|
||||||
account_id: current_account.id,
|
account_id: Current.account.id,
|
||||||
inbox_id: @contact_inbox.inbox_id,
|
inbox_id: @contact_inbox.inbox_id,
|
||||||
contact_id: @contact_inbox.contact_id,
|
contact_id: @contact_inbox.contact_id,
|
||||||
contact_inbox_id: @contact_inbox.id
|
contact_inbox_id: @contact_inbox.id
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
|
class Api::V1::Accounts::FacebookIndicatorsController < Api::V1::Accounts::BaseController
|
||||||
before_action :set_access_token
|
before_action :set_access_token
|
||||||
around_action :handle_with_exception
|
around_action :handle_with_exception
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def inbox
|
def inbox
|
||||||
@inbox ||= current_account.inboxes.find(permitted_params[:inbox_id])
|
@inbox ||= Current.account.inboxes.find(permitted_params[:inbox_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_access_token
|
def set_access_token
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseController
|
||||||
before_action :fetch_inbox, only: [:create, :show]
|
before_action :fetch_inbox, only: [:create, :show]
|
||||||
before_action :current_agents_ids, only: [:create]
|
before_action :current_agents_ids, only: [:create]
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@agents = current_account.users.where(id: @inbox.members.pluck(:user_id))
|
@agents = Current.account.users.where(id: @inbox.members.pluck(:user_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -40,6 +40,6 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_inbox
|
def fetch_inbox
|
||||||
@inbox = current_account.inboxes.find(params[:inbox_id])
|
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
class Api::V1::Accounts::InboxesController < Api::BaseController
|
class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||||
before_action :current_account
|
|
||||||
before_action :fetch_inbox, except: [:index, :create]
|
before_action :fetch_inbox, except: [:index, :create]
|
||||||
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@inboxes = policy_scope(current_account.inboxes)
|
@inboxes = policy_scope(Current.account.inboxes)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
channel = web_widgets.create!(permitted_params[:channel].except(:type)) if permitted_params[:channel][:type] == 'web_widget'
|
channel = web_widgets.create!(permitted_params[:channel].except(:type)) if permitted_params[:channel][:type] == 'web_widget'
|
||||||
@inbox = current_account.inboxes.build(name: permitted_params[:name], channel: channel)
|
@inbox = Current.account.inboxes.build(
|
||||||
|
name: permitted_params[:name],
|
||||||
|
greeting_message: permitted_params[:greeting_message],
|
||||||
|
greeting_enabled: permitted_params[:greeting_enabled],
|
||||||
|
channel: channel
|
||||||
|
)
|
||||||
@inbox.avatar.attach(permitted_params[:avatar])
|
@inbox.avatar.attach(permitted_params[:avatar])
|
||||||
@inbox.save!
|
@inbox.save!
|
||||||
end
|
end
|
||||||
|
@ -41,7 +45,7 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def fetch_inbox
|
def fetch_inbox
|
||||||
@inbox = current_account.inboxes.find(params[:id])
|
@inbox = Current.account.inboxes.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_agent_bot
|
def fetch_agent_bot
|
||||||
|
@ -49,7 +53,7 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def web_widgets
|
def web_widgets
|
||||||
current_account.web_widgets
|
Current.account.web_widgets
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_authorization
|
def check_authorization
|
||||||
|
@ -57,11 +61,12 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:id, :avatar, :name, channel: [:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :agent_away_message])
|
params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, channel:
|
||||||
|
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline])
|
||||||
end
|
end
|
||||||
|
|
||||||
def inbox_update_params
|
def inbox_update_params
|
||||||
params.permit(:enable_auto_assignment, :name, :avatar, channel: [:website_url, :widget_color, :welcome_title,
|
params.permit(:enable_auto_assignment, :name, :avatar, :greeting_message, :greeting_enabled,
|
||||||
:welcome_tagline, :agent_away_message])
|
channel: [:website_url, :widget_color, :welcome_title, :welcome_tagline])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
class Api::V1::Accounts::Integrations::AppsController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :fetch_apps, only: [:index]
|
||||||
|
before_action :fetch_app, only: [:show]
|
||||||
|
|
||||||
|
def index; end
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_apps
|
||||||
|
@apps = Integrations::App.all.select(&:active?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_app
|
||||||
|
@app = Integrations::App.find(id: params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,38 @@
|
||||||
|
class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :fetch_hook, only: [:update, :destroy]
|
||||||
|
|
||||||
|
def create
|
||||||
|
builder = Integrations::Slack::HookBuilder.new(
|
||||||
|
account: current_account,
|
||||||
|
code: params[:code],
|
||||||
|
inbox_id: params[:inbox_id]
|
||||||
|
)
|
||||||
|
@hook = builder.perform
|
||||||
|
create_chatwoot_slack_channel
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
create_chatwoot_slack_channel
|
||||||
|
render json: @hook
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@hook.destroy
|
||||||
|
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_hook
|
||||||
|
@hook = Integrations::Hook.find_by(app_id: 'slack')
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_chatwoot_slack_channel
|
||||||
|
channel = params[:channel] || 'customer-conversations'
|
||||||
|
builder = Integrations::Slack::ChannelBuilder.new(
|
||||||
|
hook: @hook, channel: channel
|
||||||
|
)
|
||||||
|
builder.perform
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,10 +1,38 @@
|
||||||
class Api::V1::Accounts::LabelsController < Api::BaseController
|
class Api::V1::Accounts::LabelsController < Api::V1::Accounts::BaseController
|
||||||
# list all labels in account
|
before_action :current_account
|
||||||
|
before_action :fetch_label, except: [:index, :create]
|
||||||
|
before_action :check_authorization
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@labels = current_account.all_conversation_tags
|
@labels = policy_scope(Current.account.labels)
|
||||||
end
|
end
|
||||||
|
|
||||||
def most_used
|
def show; end
|
||||||
@labels = ActsAsTaggableOn::Tag.most_used(params[:count] || 10)
|
|
||||||
|
def create
|
||||||
|
@label = Current.account.labels.create!(permitted_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@label.update!(permitted_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@label.destroy
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_label
|
||||||
|
@label = Current.account.labels.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_authorization
|
||||||
|
authorize(Label)
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.require(:label).permit(:title, :description, :color, :show_on_sidebar)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
class Api::V1::Accounts::NotificationSettingsController < Api::V1::Accounts::BaseController
|
||||||
before_action :set_user, :load_notification_setting
|
before_action :set_user, :load_notification_setting
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
@ -16,7 +16,7 @@ class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_notification_setting
|
def load_notification_setting
|
||||||
@notification_setting = @user.notification_settings.find_by(account_id: current_account.id)
|
@notification_setting = @user.notification_settings.find_by(account_id: Current.account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def notification_setting_params
|
def notification_setting_params
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
class Api::V1::Accounts::NotificationsController < Api::BaseController
|
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
before_action :fetch_notification, only: [:update]
|
before_action :fetch_notification, only: [:update]
|
||||||
|
before_action :set_primary_actor, only: [:read_all]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@notifications = current_user.notifications.where(account_id: current_account.id)
|
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
||||||
render json: @notifications
|
@notifications = current_user.notifications.where(account_id: current_account.id).page params[:page]
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_all
|
||||||
|
if @primary_actor
|
||||||
|
current_user.notifications.where(account_id: current_account.id, primary_actor: @primary_actor, read_at: nil)
|
||||||
|
.update(read_at: DateTime.now.utc)
|
||||||
|
else
|
||||||
|
current_user.notifications.where(account_id: current_account.id, read_at: nil).update(read_at: DateTime.now.utc)
|
||||||
|
end
|
||||||
|
|
||||||
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@ -15,6 +27,13 @@ class Api::V1::Accounts::NotificationsController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_primary_actor
|
||||||
|
return unless params[:primary_actor_type]
|
||||||
|
return unless Notification::PRIMARY_ACTORS.include?(params[:primary_actor_type])
|
||||||
|
|
||||||
|
@primary_actor = params[:primary_actor_type].safe_constantize.find_by(id: params[:primary_actor_id])
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_notification
|
def fetch_notification
|
||||||
@notification = current_user.notifications.find(params[:id])
|
@notification = current_user.notifications.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
class Api::V1::Accounts::SubscriptionsController < Api::BaseController
|
|
||||||
skip_before_action :check_subscription
|
|
||||||
|
|
||||||
before_action :check_billing_enabled
|
|
||||||
|
|
||||||
def index
|
|
||||||
render json: current_account.subscription_data
|
|
||||||
end
|
|
||||||
|
|
||||||
def status
|
|
||||||
render json: current_account.subscription.summary
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,14 +1,13 @@
|
||||||
class Api::V1::Accounts::WebhooksController < Api::BaseController
|
class Api::V1::Accounts::WebhooksController < Api::V1::Accounts::BaseController
|
||||||
before_action :current_account
|
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :fetch_webhook, only: [:update, :destroy]
|
before_action :fetch_webhook, only: [:update, :destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@webhooks = current_account.webhooks
|
@webhooks = Current.account.webhooks
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@webhook = current_account.webhooks.new(webhook_params)
|
@webhook = Current.account.webhooks.new(webhook_params)
|
||||||
@webhook.save!
|
@webhook.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ class Api::V1::Accounts::WebhooksController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_webhook
|
def fetch_webhook
|
||||||
@webhook = current_account.webhooks.find(params[:id])
|
@webhook = Current.account.webhooks.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_authorization
|
def check_authorization
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
class Api::V1::Accounts::AccountsController < Api::BaseController
|
class Api::V1::AccountsController < Api::BaseController
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
|
|
||||||
skip_before_action :verify_authenticity_token, only: [:create]
|
skip_before_action :verify_authenticity_token, only: [:create]
|
||||||
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
|
||||||
only: [:create], raise: false
|
only: [:create], raise: false
|
||||||
before_action :check_signup_enabled, only: [:create]
|
before_action :check_signup_enabled, only: [:create]
|
||||||
before_action :fetch_account, except: [:create]
|
before_action :fetch_account, except: [:create]
|
|
@ -1,6 +1,5 @@
|
||||||
class Api::V1::AgentBotsController < Api::BaseController
|
class Api::V1::AgentBotsController < Api::BaseController
|
||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_before_action :check_subscription
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: AgentBot.all
|
render json: AgentBot.all
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
class Api::V1::Integrations::WebhooksController < ApplicationController
|
||||||
|
def create
|
||||||
|
builder = Integrations::Slack::IncomingMessageBuilder.new(params)
|
||||||
|
response = builder.perform
|
||||||
|
render json: response
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,6 +16,6 @@ class Api::V1::ProfilesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def profile_params
|
def profile_params
|
||||||
params.require(:profile).permit(:email, :name, :password, :password_confirmation, :avatar)
|
params.require(:profile).permit(:email, :name, :password, :password_confirmation, :avatar, :availability)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
class Api::V1::WebhooksController < ApplicationController
|
class Api::V1::WebhooksController < ApplicationController
|
||||||
skip_before_action :authenticate_user!, raise: false
|
skip_before_action :authenticate_user!, raise: false
|
||||||
skip_before_action :set_current_user
|
skip_before_action :set_current_user
|
||||||
skip_before_action :check_subscription
|
|
||||||
|
|
||||||
before_action :login_from_basic_auth, only: [:chargebee]
|
|
||||||
before_action :check_billing_enabled, only: [:chargebee]
|
|
||||||
|
|
||||||
def chargebee
|
|
||||||
chargebee_consumer.consume
|
|
||||||
head :ok
|
|
||||||
rescue StandardError => e
|
|
||||||
Raven.capture_exception(e)
|
|
||||||
head :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def twitter_crc
|
def twitter_crc
|
||||||
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
|
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
|
||||||
|
@ -34,16 +22,6 @@ class Api::V1::WebhooksController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def login_from_basic_auth
|
|
||||||
authenticate_or_request_with_http_basic do |username, password|
|
|
||||||
username == ENV['CHARGEBEE_WEBHOOK_USERNAME'] && password == ENV['CHARGEBEE_WEBHOOK_PASSWORD']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def chargebee_consumer
|
|
||||||
@chargebee_consumer ||= ::Webhooks::Chargebee.new(params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def twitter_consumer
|
def twitter_consumer
|
||||||
@twitter_consumer ||= ::Webhooks::Twitter.new(params)
|
@twitter_consumer ||= ::Webhooks::Twitter.new(params)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
class Api::V1::Widget::BaseController < ApplicationController
|
class Api::V1::Widget::BaseController < ApplicationController
|
||||||
|
before_action :set_web_widget
|
||||||
|
before_action :set_contact
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
||||||
before_action :set_web_widget
|
|
||||||
before_action :set_contact
|
|
||||||
|
|
||||||
def update
|
def update
|
||||||
contact_identify_action = ContactIdentifyAction.new(
|
contact_identify_action = ContactIdentifyAction.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||||
include Events::Types
|
include Events::Types
|
||||||
before_action :set_web_widget
|
|
||||||
before_action :set_contact
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@conversation = conversation
|
@conversation = conversation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_last_seen
|
||||||
|
head :ok && return if conversation.nil?
|
||||||
|
|
||||||
|
conversation.user_last_seen_at = DateTime.now.utc
|
||||||
|
conversation.save!
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
def toggle_typing
|
def toggle_typing
|
||||||
head :ok && return if conversation.nil?
|
head :ok && return if conversation.nil?
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
class Api::V1::Widget::EventsController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::EventsController < Api::V1::Widget::BaseController
|
||||||
include Events::Types
|
include Events::Types
|
||||||
before_action :set_web_widget
|
|
||||||
before_action :set_contact
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox)
|
Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox, event_info: event_info)
|
||||||
head :no_content
|
head :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def event_info
|
||||||
|
{
|
||||||
|
widget_language: params[:locale],
|
||||||
|
browser_language: browser.accept_language.first&.code
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:name, :website_token)
|
params.permit(:name, :website_token)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController
|
||||||
before_action :set_web_widget
|
skip_before_action :set_contact
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@inbox_members = @web_widget.inbox.inbox_members.includes(:user)
|
@inbox_members = @web_widget.inbox.inbox_members.includes(:user)
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController
|
||||||
before_action :set_web_widget
|
|
||||||
before_action :set_contact
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
conversation.label_list.add(permitted_params[:label])
|
conversation.label_list.add(permitted_params[:label])
|
||||||
conversation.save!
|
conversation.save!
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||||
before_action :set_web_widget
|
|
||||||
before_action :set_contact
|
|
||||||
before_action :set_conversation, only: [:create]
|
before_action :set_conversation, only: [:create]
|
||||||
before_action :set_message, only: [:update]
|
before_action :set_message, only: [:update]
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||||
def message_params
|
def message_params
|
||||||
{
|
{
|
||||||
account_id: conversation.account_id,
|
account_id: conversation.account_id,
|
||||||
contact_id: @contact.id,
|
sender: @contact,
|
||||||
content: permitted_params[:message][:content],
|
content: permitted_params[:message][:content],
|
||||||
inbox_id: conversation.inbox_id,
|
inbox_id: conversation.inbox_id,
|
||||||
message_type: :incoming
|
message_type: :incoming
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Api::V2::Accounts::ReportsController < Api::BaseController
|
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||||
def account
|
def account
|
||||||
builder = V2::ReportBuilder.new(current_account, account_report_params)
|
builder = V2::ReportBuilder.new(Current.account, account_report_params)
|
||||||
data = builder.build
|
data = builder.build
|
||||||
render json: data
|
render json: data
|
||||||
end
|
end
|
||||||
|
@ -29,7 +29,7 @@ class Api::V2::Accounts::ReportsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_summary_metrics
|
def account_summary_metrics
|
||||||
builder = V2::ReportBuilder.new(current_account, account_summary_params)
|
builder = V2::ReportBuilder.new(Current.account, account_summary_params)
|
||||||
builder.summary
|
builder.summary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
class ApiController < ApplicationController
|
class ApiController < ApplicationController
|
||||||
skip_before_action :set_current_user, only: [:index]
|
skip_before_action :set_current_user, only: [:index]
|
||||||
skip_before_action :check_subscription, only: [:index]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: { version: Chatwoot.config[:version], timestamp: Time.now.utc.to_formatted_s(:db) }
|
render json: { version: Chatwoot.config[:version], timestamp: Time.now.utc.to_formatted_s(:db) }
|
||||||
|
|
|
@ -5,7 +5,6 @@ class ApplicationController < ActionController::Base
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
before_action :set_current_user, unless: :devise_controller?
|
before_action :set_current_user, unless: :devise_controller?
|
||||||
before_action :check_subscription, unless: :devise_controller?
|
|
||||||
around_action :handle_with_exception, unless: :devise_controller?
|
around_action :handle_with_exception, unless: :devise_controller?
|
||||||
|
|
||||||
# after_action :verify_authorized
|
# after_action :verify_authorized
|
||||||
|
@ -13,40 +12,6 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def current_account
|
|
||||||
@current_account ||= find_current_account
|
|
||||||
Current.account = @current_account
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_current_account
|
|
||||||
account = Account.find(params[:account_id])
|
|
||||||
if current_user
|
|
||||||
account_accessible_for_user?(account)
|
|
||||||
elsif @resource&.is_a?(AgentBot)
|
|
||||||
account_accessible_for_bot?(account)
|
|
||||||
end
|
|
||||||
switch_locale account
|
|
||||||
account
|
|
||||||
end
|
|
||||||
|
|
||||||
def switch_locale(account)
|
|
||||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
|
||||||
locale ||= (I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil)
|
|
||||||
# if local is not set in param, lets try account
|
|
||||||
locale ||= (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil)
|
|
||||||
I18n.locale = locale || I18n.default_locale
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_accessible_for_user?(account)
|
|
||||||
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
|
||||||
Current.account_user = @current_account_user
|
|
||||||
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
|
||||||
end
|
|
||||||
|
|
||||||
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 handle_with_exception
|
def handle_with_exception
|
||||||
yield
|
yield
|
||||||
rescue ActiveRecord::RecordNotFound => e
|
rescue ActiveRecord::RecordNotFound => e
|
||||||
|
@ -65,7 +30,7 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_subscription
|
def current_subscription
|
||||||
@subscription ||= current_account.subscription
|
@subscription ||= Current.account.subscription
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_unauthorized(message)
|
def render_unauthorized(message)
|
||||||
|
@ -94,16 +59,20 @@ class ApplicationController < ActionController::Base
|
||||||
render json: exception.to_hash, status: exception.http_status
|
render json: exception.to_hash, status: exception.http_status
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_subscription
|
def locale_from_params
|
||||||
# This block is left over from the initial version of chatwoot
|
I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil
|
||||||
# We might reuse this later in the hosted version of chatwoot.
|
|
||||||
return if !ENV['BILLING_ENABLED'] || !current_user
|
|
||||||
|
|
||||||
if current_subscription.trial? && current_subscription.expiry < Date.current
|
|
||||||
render json: { error: 'Trial Expired' }, status: :trial_expired
|
|
||||||
elsif current_subscription.cancelled?
|
|
||||||
render json: { error: 'Account Suspended' }, status: :account_suspended
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def locale_from_account(account)
|
||||||
|
I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def switch_locale(account)
|
||||||
|
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||||
|
locale ||= locale_from_params
|
||||||
|
# if local is not set in param, lets try account
|
||||||
|
locale ||= locale_from_account(account)
|
||||||
|
I18n.locale = locale || I18n.default_locale
|
||||||
end
|
end
|
||||||
|
|
||||||
def pundit_user
|
def pundit_user
|
||||||
|
|
|
@ -4,6 +4,7 @@ class WidgetsController < ActionController::Base
|
||||||
before_action :set_token
|
before_action :set_token
|
||||||
before_action :set_contact
|
before_action :set_contact
|
||||||
before_action :build_contact
|
before_action :build_contact
|
||||||
|
after_action :allow_iframe_requests
|
||||||
|
|
||||||
def index; end
|
def index; end
|
||||||
|
|
||||||
|
@ -50,4 +51,8 @@ class WidgetsController < ActionController::Base
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:website_token, :cw_conversation)
|
params.permit(:website_token, :cw_conversation)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allow_iframe_requests
|
||||||
|
response.headers.delete('X-Frame-Options')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,8 +9,7 @@ class AsyncDispatcher < BaseDispatcher
|
||||||
end
|
end
|
||||||
|
|
||||||
def listeners
|
def listeners
|
||||||
listeners = [EventListener.instance, WebhookListener.instance]
|
listeners = [EventListener.instance, WebhookListener.instance, HookListener.instance]
|
||||||
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
|
||||||
listeners
|
listeners
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ConversationFinder
|
||||||
|
|
||||||
def find_all_conversations
|
def find_all_conversations
|
||||||
@conversations = current_account.conversations.includes(
|
@conversations = current_account.conversations.includes(
|
||||||
:assignee, :contact, :inbox
|
:assignee, :inbox, contact: [:avatar_attachment]
|
||||||
).where(inbox_id: @inbox_ids)
|
).where(inbox_id: @inbox_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
module Api::V1::SubscriptionsHelper
|
|
||||||
end
|
|
|
@ -10,6 +10,10 @@ class ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
get url() {
|
get url() {
|
||||||
|
return `${this.baseUrl()}/${this.resource}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUrl() {
|
||||||
let url = this.apiVersion;
|
let url = this.apiVersion;
|
||||||
if (this.options.accountScoped) {
|
if (this.options.accountScoped) {
|
||||||
const isInsideAccountScopedURLs = window.location.pathname.includes(
|
const isInsideAccountScopedURLs = window.location.pathname.includes(
|
||||||
|
@ -21,7 +25,8 @@ class ApiClient {
|
||||||
url = `${url}/accounts/${accountId}`;
|
url = `${url}/accounts/${accountId}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${url}/${this.resource}`;
|
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
get() {
|
||||||
|
|
|
@ -118,21 +118,18 @@ export default {
|
||||||
return axios.post(urlData.url, { email });
|
return axios.post(urlData.url, { email });
|
||||||
},
|
},
|
||||||
|
|
||||||
profileUpdate({ name, email, password, password_confirmation, avatar }) {
|
profileUpdate({ password, password_confirmation, ...profileAttributes }) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (name) {
|
Object.keys(profileAttributes).forEach(key => {
|
||||||
formData.append('profile[name]', name);
|
const value = profileAttributes[key];
|
||||||
}
|
if (value) {
|
||||||
if (email) {
|
formData.append(`profile[${key}]`, value);
|
||||||
formData.append('profile[email]', email);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (password && password_confirmation) {
|
if (password && password_confirmation) {
|
||||||
formData.append('profile[password]', password);
|
formData.append('profile[password]', password);
|
||||||
formData.append('profile[password_confirmation]', password_confirmation);
|
formData.append('profile[password_confirmation]', password_confirmation);
|
||||||
}
|
}
|
||||||
if (avatar) {
|
|
||||||
formData.append('profile[avatar]', avatar);
|
|
||||||
}
|
|
||||||
return axios.put(endPoints('profileUpdate').url, formData);
|
return axios.put(endPoints('profileUpdate').url, formData);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
/* global axios */
|
|
||||||
|
|
||||||
import endPoints from './endPoints';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getSubscription() {
|
|
||||||
const urlData = endPoints('subscriptions').get();
|
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.get(urlData.url)
|
|
||||||
.then(response => {
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return fetchPromise;
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -6,20 +6,6 @@ class FBChannel extends ApiClient {
|
||||||
super('facebook_indicators', { accountScoped: true });
|
super('facebook_indicators', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
markSeen({ inboxId, contactId }) {
|
|
||||||
return axios.post(`${this.url}/mark_seen`, {
|
|
||||||
inbox_id: inboxId,
|
|
||||||
contact_id: contactId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTyping({ status, inboxId, contactId }) {
|
|
||||||
return axios.post(`${this.url}/typing_${status}`, {
|
|
||||||
inbox_id: inboxId,
|
|
||||||
contact_id: contactId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
create(params) {
|
create(params) {
|
||||||
return axios.post(
|
return axios.post(
|
||||||
`${this.url.replace(this.resource, '')}callbacks/register_facebook_page`,
|
`${this.url.replace(this.resource, '')}callbacks/register_facebook_page`,
|
||||||
|
|
|
@ -33,14 +33,6 @@ const endPoints = {
|
||||||
},
|
},
|
||||||
params: { omniauth_token: '' },
|
params: { omniauth_token: '' },
|
||||||
},
|
},
|
||||||
|
|
||||||
subscriptions: {
|
|
||||||
get() {
|
|
||||||
return {
|
|
||||||
url: '/api/v1/subscriptions',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default page => {
|
export default page => {
|
||||||
|
|
|
@ -6,13 +6,14 @@ class ConversationApi extends ApiClient {
|
||||||
super('conversations', { accountScoped: true });
|
super('conversations', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
get({ inboxId, status, assigneeType, page }) {
|
get({ inboxId, status, assigneeType, page, labels }) {
|
||||||
return axios.get(this.url, {
|
return axios.get(this.url, {
|
||||||
params: {
|
params: {
|
||||||
inbox_id: inboxId,
|
inbox_id: inboxId,
|
||||||
status,
|
status,
|
||||||
assignee_type: assigneeType,
|
assignee_type: assigneeType,
|
||||||
page,
|
page,
|
||||||
|
labels,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -43,6 +44,17 @@ class ConversationApi extends ApiClient {
|
||||||
mute(conversationId) {
|
mute(conversationId) {
|
||||||
return axios.post(`${this.url}/${conversationId}/mute`);
|
return axios.post(`${this.url}/${conversationId}/mute`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta({ inboxId, status, assigneeType, labels }) {
|
||||||
|
return axios.get(`${this.url}/meta`, {
|
||||||
|
params: {
|
||||||
|
inbox_id: inboxId,
|
||||||
|
status,
|
||||||
|
assignee_type: assigneeType,
|
||||||
|
labels,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ConversationApi();
|
export default new ConversationApi();
|
||||||
|
|
21
app/javascript/dashboard/api/integrations.js
Normal file
21
app/javascript/dashboard/api/integrations.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* global axios */
|
||||||
|
|
||||||
|
import ApiClient from './ApiClient';
|
||||||
|
|
||||||
|
class IntegrationsAPI extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('integrations/apps', { accountScoped: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
connectSlack(code) {
|
||||||
|
return axios.post(`${this.baseUrl()}/integrations/slack`, {
|
||||||
|
code: code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(integrationId) {
|
||||||
|
return axios.delete(`${this.baseUrl()}/integrations/${integrationId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new IntegrationsAPI();
|
9
app/javascript/dashboard/api/labels.js
Normal file
9
app/javascript/dashboard/api/labels.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import ApiClient from './ApiClient';
|
||||||
|
|
||||||
|
class LabelsAPI extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('labels', { accountScoped: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LabelsAPI();
|
|
@ -9,7 +9,5 @@ describe('#FBChannel', () => {
|
||||||
expect(fbChannel).toHaveProperty('create');
|
expect(fbChannel).toHaveProperty('create');
|
||||||
expect(fbChannel).toHaveProperty('update');
|
expect(fbChannel).toHaveProperty('update');
|
||||||
expect(fbChannel).toHaveProperty('delete');
|
expect(fbChannel).toHaveProperty('delete');
|
||||||
expect(fbChannel).toHaveProperty('markSeen');
|
|
||||||
expect(fbChannel).toHaveProperty('toggleTyping');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import agents from '../contacts';
|
import contacts from '../contacts';
|
||||||
import ApiClient from '../ApiClient';
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
describe('#ContactsAPI', () => {
|
describe('#ContactsAPI', () => {
|
||||||
it('creates correct instance', () => {
|
it('creates correct instance', () => {
|
||||||
expect(agents).toBeInstanceOf(ApiClient);
|
expect(contacts).toBeInstanceOf(ApiClient);
|
||||||
expect(agents).toHaveProperty('get');
|
expect(contacts).toHaveProperty('get');
|
||||||
expect(agents).toHaveProperty('show');
|
expect(contacts).toHaveProperty('show');
|
||||||
expect(agents).toHaveProperty('create');
|
expect(contacts).toHaveProperty('create');
|
||||||
expect(agents).toHaveProperty('update');
|
expect(contacts).toHaveProperty('update');
|
||||||
expect(agents).toHaveProperty('delete');
|
expect(contacts).toHaveProperty('delete');
|
||||||
expect(agents).toHaveProperty('getConversations');
|
expect(contacts).toHaveProperty('getConversations');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import conversationAPI from '../../inbox/conversation';
|
||||||
|
import ApiClient from '../../ApiClient';
|
||||||
|
|
||||||
|
describe('#ConversationAPI', () => {
|
||||||
|
it('creates correct instance', () => {
|
||||||
|
expect(conversationAPI).toBeInstanceOf(ApiClient);
|
||||||
|
expect(conversationAPI).toHaveProperty('get');
|
||||||
|
expect(conversationAPI).toHaveProperty('show');
|
||||||
|
expect(conversationAPI).toHaveProperty('create');
|
||||||
|
expect(conversationAPI).toHaveProperty('update');
|
||||||
|
expect(conversationAPI).toHaveProperty('delete');
|
||||||
|
expect(conversationAPI).toHaveProperty('toggleStatus');
|
||||||
|
expect(conversationAPI).toHaveProperty('assignAgent');
|
||||||
|
expect(conversationAPI).toHaveProperty('markMessageRead');
|
||||||
|
expect(conversationAPI).toHaveProperty('toggleTyping');
|
||||||
|
expect(conversationAPI).toHaveProperty('mute');
|
||||||
|
expect(conversationAPI).toHaveProperty('meta');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
import inboxes from '../inboxes';
|
import inboxes from '../inboxes';
|
||||||
import ApiClient from '../ApiClient';
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
describe('#AgentAPI', () => {
|
describe('#InboxesAPI', () => {
|
||||||
it('creates correct instance', () => {
|
it('creates correct instance', () => {
|
||||||
expect(inboxes).toBeInstanceOf(ApiClient);
|
expect(inboxes).toBeInstanceOf(ApiClient);
|
||||||
expect(inboxes).toHaveProperty('get');
|
expect(inboxes).toHaveProperty('get');
|
||||||
|
|
14
app/javascript/dashboard/api/specs/labels.spec.js
Normal file
14
app/javascript/dashboard/api/specs/labels.spec.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import labels from '../labels';
|
||||||
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
|
describe('#LabelsAPI', () => {
|
||||||
|
it('creates correct instance', () => {
|
||||||
|
expect(labels).toBeInstanceOf(ApiClient);
|
||||||
|
expect(labels).toHaveProperty('get');
|
||||||
|
expect(labels).toHaveProperty('show');
|
||||||
|
expect(labels).toHaveProperty('create');
|
||||||
|
expect(labels).toHaveProperty('update');
|
||||||
|
expect(labels).toHaveProperty('delete');
|
||||||
|
expect(labels.url).toBe('/api/v1/labels');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
.button {
|
.button {
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
font-family: $body-font-family;
|
font-family: $body-font-family;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
|
||||||
&.round {
|
&.round {
|
||||||
border-radius: 1000px;
|
border-radius: 1000px;
|
||||||
|
@ -20,10 +20,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
max-width: 15rem;
|
|
||||||
padding: $space-smaller $space-small;
|
|
||||||
border-radius: $space-smaller;
|
border-radius: $space-smaller;
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
|
max-width: 15rem;
|
||||||
|
padding: $space-smaller $space-small;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
|
|
@ -67,6 +67,7 @@ BlinkMacSystemFont,
|
||||||
"Segoe UI",
|
"Segoe UI",
|
||||||
Roboto,
|
Roboto,
|
||||||
"Helvetica Neue",
|
"Helvetica Neue",
|
||||||
|
Tahoma,
|
||||||
Arial,
|
Arial,
|
||||||
sans-serif;
|
sans-serif;
|
||||||
$body-antialiased: true;
|
$body-antialiased: true;
|
||||||
|
@ -382,7 +383,7 @@ $label-color: $primary-color;
|
||||||
$label-color-alt: $black;
|
$label-color-alt: $black;
|
||||||
$label-palette: $foundation-palette;
|
$label-palette: $foundation-palette;
|
||||||
$label-font-size: $font-size-micro;
|
$label-font-size: $font-size-micro;
|
||||||
$label-padding: $space-micro $space-smaller;
|
$label-padding: $space-smaller $space-small;
|
||||||
$label-radius: $space-micro;
|
$label-radius: $space-micro;
|
||||||
|
|
||||||
// 21. Media Object
|
// 21. Media Object
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
width: $space-medium;
|
width: $space-medium;
|
||||||
|
|
||||||
&.message {
|
&.message {
|
||||||
@include elegent-shadow;
|
@include normal-shadow;
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
border-radius: $space-large;
|
border-radius: $space-large;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
@import '~widget/assets/scss/mixins';
|
@import '~widget/assets/scss/mixins';
|
||||||
|
|
||||||
$elegant-shadow-color: rgba(49, 49, 93, 0.15);
|
|
||||||
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||||
|
|
||||||
//borders
|
//borders
|
||||||
|
@ -141,12 +140,8 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin elegent-shadow() {
|
|
||||||
box-shadow: 0 10px 25px 0 $elegant-shadow-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin elegant-card() {
|
@mixin elegant-card() {
|
||||||
@include elegent-shadow;
|
@include normal-shadow;
|
||||||
border-radius: $space-small;
|
border-radius: $space-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,28 +189,42 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||||
border-bottom: $size solid $color;
|
border-bottom: $size solid $color;
|
||||||
border-left: $size solid transparent;
|
border-left: $size solid transparent;
|
||||||
border-right: $size solid transparent;
|
border-right: $size solid transparent;
|
||||||
} @else if $direction == 'right' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'right' {
|
||||||
border-bottom: $size solid transparent;
|
border-bottom: $size solid transparent;
|
||||||
border-left: $size solid $color;
|
border-left: $size solid $color;
|
||||||
border-top: $size solid transparent;
|
border-top: $size solid transparent;
|
||||||
} @else if $direction == 'bottom' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'bottom' {
|
||||||
border-left: $size solid transparent;
|
border-left: $size solid transparent;
|
||||||
border-right: $size solid transparent;
|
border-right: $size solid transparent;
|
||||||
border-top: $size solid $color;
|
border-top: $size solid $color;
|
||||||
} @else if $direction == 'left' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'left' {
|
||||||
border-bottom: $size solid transparent;
|
border-bottom: $size solid transparent;
|
||||||
border-right: $size solid $color;
|
border-right: $size solid $color;
|
||||||
border-top: $size solid transparent;
|
border-top: $size solid transparent;
|
||||||
} @else if $direction == 'top-left' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'top-left' {
|
||||||
border-right: $size solid transparent;
|
border-right: $size solid transparent;
|
||||||
border-top: $size solid $color;
|
border-top: $size solid $color;
|
||||||
} @else if $direction == 'top-right' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'top-right' {
|
||||||
border-left: $size solid transparent;
|
border-left: $size solid transparent;
|
||||||
border-top: $size solid $color;
|
border-top: $size solid $color;
|
||||||
} @else if $direction == 'bottom-left' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'bottom-left' {
|
||||||
border-bottom: $size solid $color;
|
border-bottom: $size solid $color;
|
||||||
border-right: $size solid transparent;
|
border-right: $size solid transparent;
|
||||||
} @else if $direction == 'bottom-right' {
|
}
|
||||||
|
|
||||||
|
@else if $direction == 'bottom-right' {
|
||||||
border-bottom: $size solid $color;
|
border-bottom: $size solid $color;
|
||||||
border-left: $size solid transparent;
|
border-left: $size solid transparent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
@import 'animations';
|
@import 'animations';
|
||||||
|
|
||||||
@import 'foundation-custom';
|
@import 'foundation-custom';
|
||||||
@import 'widgets/billing';
|
|
||||||
@import 'widgets/buttons';
|
@import 'widgets/buttons';
|
||||||
@import 'widgets/conv-header';
|
@import 'widgets/conv-header';
|
||||||
@import 'widgets/conversation-card';
|
@import 'widgets/conversation-card';
|
||||||
|
|
|
@ -3,16 +3,17 @@
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
border: 1px solid $color-border;
|
border: 1px solid $color-border;
|
||||||
border-radius: $space-smaller;
|
border-radius: $space-smaller;
|
||||||
|
margin-bottom: $space-normal;
|
||||||
padding: $space-normal;
|
padding: $space-normal;
|
||||||
|
|
||||||
.integration--image {
|
.integration--image {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: $space-normal;
|
margin-right: $space-normal;
|
||||||
width: 8rem;
|
width: 10rem;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 8rem;
|
max-width: 100%;
|
||||||
padding: $space-small;
|
padding: $space-medium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
.billing {
|
|
||||||
@include full-height;
|
|
||||||
|
|
||||||
.row {
|
|
||||||
@include full-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
.billing__stats {
|
|
||||||
@include flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.billing__form {
|
|
||||||
@include thin-border($color-border-light);
|
|
||||||
@include margin($zero - $space-micro);
|
|
||||||
@include full-height;
|
|
||||||
background: $color-white;
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
@include full-height;
|
|
||||||
border: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-row {
|
|
||||||
@include padding($space-normal);
|
|
||||||
@include flex;
|
|
||||||
flex-direction: column;
|
|
||||||
// @include thin-border($color-border-light);
|
|
||||||
// @include margin(-$space-micro $zero);
|
|
||||||
background: $color-white;
|
|
||||||
font-size: $font-size-small;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: $color-heading;
|
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: $font-size-mega;
|
|
||||||
font-weight: $font-weight-light;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-locked {
|
|
||||||
@include background-gray;
|
|
||||||
@include margin(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lock-message {
|
|
||||||
@include flex;
|
|
||||||
@include full-height;
|
|
||||||
flex-direction: column;
|
|
||||||
@include flex-align(center, middle);
|
|
||||||
|
|
||||||
div {
|
|
||||||
@include flex;
|
|
||||||
@include full-height;
|
|
||||||
flex-direction: column;
|
|
||||||
@include flex-align(center, middle);
|
|
||||||
|
|
||||||
img {
|
|
||||||
@include margin($space-normal);
|
|
||||||
width: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: $font-size-small;
|
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -66,6 +66,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.resolve--button {
|
.button.resolve--button {
|
||||||
|
@include flex-align($x: center, $y: middle);
|
||||||
|
|
||||||
width: 13.2rem;
|
width: 13.2rem;
|
||||||
|
|
||||||
>.icon {
|
>.icon {
|
||||||
|
|
|
@ -78,6 +78,11 @@
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-from-agent {
|
||||||
|
color: $color-gray;
|
||||||
|
font-size: $font-size-mini;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation--meta {
|
.conversation--meta {
|
||||||
|
@ -120,11 +125,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation--message {
|
.conversation--message {
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation--user {
|
.conversation--user {
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +309,7 @@
|
||||||
|
|
||||||
&.is-private {
|
&.is-private {
|
||||||
background: lighten($warning-color, 32%);
|
background: lighten($warning-color, 32%);
|
||||||
border: 1px solid $color-border;
|
border: 1px solid lighten($warning-color, 15%);
|
||||||
color: $color-heading;
|
color: $color-heading;
|
||||||
padding-right: $space-large;
|
padding-right: $space-large;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -67,6 +67,10 @@
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
@include padding($space-large);
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
@include padding($space-large);
|
@include padding($space-large);
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
.reply-box {
|
.reply-box {
|
||||||
@include elegant-card;
|
@include light-shadow;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
border-radius: $space-small;
|
||||||
margin: $space-normal;
|
margin: $space-normal;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
max-height: $space-jumbo * 2;
|
max-height: $space-mega * 3;
|
||||||
transition: height 2s $ease-in-out-cubic;
|
transition: box-shadow .35s $ease-in-out-cubic, height 2s $ease-in-out-cubic;
|
||||||
|
|
||||||
|
&.is-focused {
|
||||||
|
@include normal-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
.reply-box__top {
|
.reply-box__top {
|
||||||
@include flex;
|
@include flex;
|
||||||
|
@ -74,7 +79,8 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
// Override min-height : 50px in foundation
|
// Override min-height : 50px in foundation
|
||||||
//
|
//
|
||||||
min-height: 1rem;
|
max-height: $space-mega * 2.4;
|
||||||
|
min-height: 4rem;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="chat-list__top">
|
<div class="chat-list__top">
|
||||||
<h1 class="page-title">
|
<h1 class="page-title">
|
||||||
<woot-sidemenu-icon />
|
<woot-sidemenu-icon />
|
||||||
{{ inbox.name || $t('CHAT_LIST.TAB_HEADING') }}
|
{{ pageTitle }}
|
||||||
</h1>
|
</h1>
|
||||||
<chat-filter @statusFilterChange="updateStatusType" />
|
<chat-filter @statusFilterChange="updateStatusType" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,14 +15,15 @@
|
||||||
@chatTabChange="updateAssigneeTab"
|
@chatTabChange="updateAssigneeTab"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p v-if="!chatListLoading && !getChatsForTab().length" class="content-box">
|
<p v-if="!chatListLoading && !conversationList.length" class="content-box">
|
||||||
{{ $t('CHAT_LIST.LIST.404') }}
|
{{ $t('CHAT_LIST.LIST.404') }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="conversations-list">
|
<div class="conversations-list">
|
||||||
<conversation-card
|
<conversation-card
|
||||||
v-for="chat in getChatsForTab()"
|
v-for="chat in conversationList"
|
||||||
:key="chat.id"
|
:key="chat.id"
|
||||||
|
:active-label="label"
|
||||||
:chat="chat"
|
:chat="chat"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@
|
||||||
|
|
||||||
<p
|
<p
|
||||||
v-if="
|
v-if="
|
||||||
getChatsForTab().length &&
|
conversationList.length &&
|
||||||
hasCurrentPageEndReached &&
|
hasCurrentPageEndReached &&
|
||||||
!chatListLoading
|
!chatListLoading
|
||||||
"
|
"
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
<script>
|
<script>
|
||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
/* eslint no-console: 0 */
|
/* eslint no-console: 0 */
|
||||||
|
/* global bus */
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import ChatFilter from './widgets/conversation/ChatFilter';
|
import ChatFilter from './widgets/conversation/ChatFilter';
|
||||||
|
@ -71,7 +73,16 @@ export default {
|
||||||
ChatFilter,
|
ChatFilter,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin, conversationMixin],
|
mixins: [timeMixin, conversationMixin],
|
||||||
props: ['conversationInbox'],
|
props: {
|
||||||
|
conversationInbox: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME,
|
activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME,
|
||||||
|
@ -87,14 +98,17 @@ export default {
|
||||||
chatListLoading: 'getChatListLoadingStatus',
|
chatListLoading: 'getChatListLoadingStatus',
|
||||||
currentUserID: 'getCurrentUserID',
|
currentUserID: 'getCurrentUserID',
|
||||||
activeInbox: 'getSelectedInbox',
|
activeInbox: 'getSelectedInbox',
|
||||||
convStats: 'getConvTabStats',
|
conversationStats: 'conversationStats/getStats',
|
||||||
}),
|
}),
|
||||||
assigneeTabItems() {
|
assigneeTabItems() {
|
||||||
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => ({
|
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => {
|
||||||
|
const count = this.conversationStats[item.COUNT_KEY] || 0;
|
||||||
|
return {
|
||||||
key: item.KEY,
|
key: item.KEY,
|
||||||
name: item.NAME,
|
name: item.NAME,
|
||||||
count: this.convStats[item.COUNT_KEY] || 0,
|
count,
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
inbox() {
|
inbox() {
|
||||||
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
|
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
|
||||||
|
@ -109,16 +123,61 @@ export default {
|
||||||
this.activeAssigneeTab
|
this.activeAssigneeTab
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
conversationFilters() {
|
||||||
|
return {
|
||||||
|
inboxId: this.conversationInbox ? this.conversationInbox : undefined,
|
||||||
|
assigneeType: this.activeAssigneeTab,
|
||||||
|
status: this.activeStatus,
|
||||||
|
page: this.currentPage + 1,
|
||||||
|
labels: this.label ? [this.label] : undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
pageTitle() {
|
||||||
|
if (this.inbox.name) {
|
||||||
|
return this.inbox.name;
|
||||||
|
}
|
||||||
|
if (this.label) {
|
||||||
|
return `#${this.label}`;
|
||||||
|
}
|
||||||
|
return this.$t('CHAT_LIST.TAB_HEADING');
|
||||||
|
},
|
||||||
|
conversationList() {
|
||||||
|
let conversationList = [];
|
||||||
|
if (this.activeAssigneeTab === 'me') {
|
||||||
|
conversationList = this.mineChatsList.slice();
|
||||||
|
} else if (this.activeAssigneeTab === 'unassigned') {
|
||||||
|
conversationList = this.unAssignedChatsList.slice();
|
||||||
|
} else {
|
||||||
|
conversationList = this.allChatList.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.label) {
|
||||||
|
return conversationList;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversationList.filter(conversation => {
|
||||||
|
const labels = this.$store.getters[
|
||||||
|
'conversationLabels/getConversationLabels'
|
||||||
|
](conversation.id);
|
||||||
|
return labels.includes(this.label);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
conversationInbox() {
|
conversationInbox() {
|
||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
},
|
},
|
||||||
|
label() {
|
||||||
|
this.resetAndFetchData();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('setChatFilter', this.activeStatus);
|
this.$store.dispatch('setChatFilter', this.activeStatus);
|
||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
this.$store.dispatch('agents/get');
|
|
||||||
|
bus.$on('fetch_conversation_stats', () => {
|
||||||
|
this.$store.dispatch('conversationStats/get', this.conversationFilters);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resetAndFetchData() {
|
resetAndFetchData() {
|
||||||
|
@ -127,12 +186,7 @@ export default {
|
||||||
this.fetchConversations();
|
this.fetchConversations();
|
||||||
},
|
},
|
||||||
fetchConversations() {
|
fetchConversations() {
|
||||||
this.$store.dispatch('fetchAllConversations', {
|
this.$store.dispatch('fetchAllConversations', this.conversationFilters);
|
||||||
inboxId: this.conversationInbox ? this.conversationInbox : undefined,
|
|
||||||
assigneeType: this.activeAssigneeTab,
|
|
||||||
status: this.activeStatus,
|
|
||||||
page: this.currentPage + 1,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
updateAssigneeTab(selectedTab) {
|
updateAssigneeTab(selectedTab) {
|
||||||
if (this.activeAssigneeTab !== selectedTab) {
|
if (this.activeAssigneeTab !== selectedTab) {
|
||||||
|
@ -148,17 +202,6 @@ export default {
|
||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getChatsForTab() {
|
|
||||||
let copyList = [];
|
|
||||||
if (this.activeAssigneeTab === 'me') {
|
|
||||||
copyList = this.mineChatsList.slice();
|
|
||||||
} else if (this.activeAssigneeTab === 'unassigned') {
|
|
||||||
copyList = this.unAssignedChatsList.slice();
|
|
||||||
} else {
|
|
||||||
copyList = this.allChatList.slice();
|
|
||||||
}
|
|
||||||
return copyList;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Code from './Code';
|
||||||
import ColorPicker from './widgets/ColorPicker';
|
import ColorPicker from './widgets/ColorPicker';
|
||||||
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
||||||
import Input from './widgets/forms/Input.vue';
|
import Input from './widgets/forms/Input.vue';
|
||||||
|
import Label from './widgets/Label.vue';
|
||||||
import LoadingState from './widgets/LoadingState';
|
import LoadingState from './widgets/LoadingState';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import ModalHeader from './ModalHeader';
|
import ModalHeader from './ModalHeader';
|
||||||
|
@ -25,6 +26,7 @@ const WootUIKit = {
|
||||||
DeleteModal,
|
DeleteModal,
|
||||||
Input,
|
Input,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
|
Label,
|
||||||
Modal,
|
Modal,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
ReportStatsCard,
|
ReportStatsCard,
|
||||||
|
|
|
@ -18,21 +18,14 @@
|
||||||
:key="inboxSection.toState"
|
:key="inboxSection.toState"
|
||||||
:menu-item="inboxSection"
|
:menu-item="inboxSection"
|
||||||
/>
|
/>
|
||||||
|
<sidebar-item
|
||||||
|
v-if="shouldShowInboxes"
|
||||||
|
:key="labelSection.toState"
|
||||||
|
:menu-item="labelSection"
|
||||||
|
/>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- this block is only required in the hosted version with billing enabled -->
|
|
||||||
<transition name="fade" mode="out-in">
|
|
||||||
<woot-status-bar
|
|
||||||
v-if="shouldShowStatusBox"
|
|
||||||
:message="trialMessage"
|
|
||||||
:button-text="$t('APP_GLOBAL.TRAIL_BUTTON')"
|
|
||||||
:button-route="{ name: 'billing' }"
|
|
||||||
:type="statusBarClass"
|
|
||||||
:show-button="isAdmin"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<div class="bottom-nav">
|
<div class="bottom-nav">
|
||||||
<transition name="menu-slide">
|
<transition name="menu-slide">
|
||||||
<div
|
<div
|
||||||
|
@ -63,7 +56,11 @@
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<div class="current-user" @click.prevent="showOptions()">
|
<div class="current-user" @click.prevent="showOptions()">
|
||||||
<thumbnail :src="currentUser.avatar_url" :username="currentUser.name" />
|
<thumbnail
|
||||||
|
:src="currentUser.avatar_url"
|
||||||
|
:username="currentUser.name"
|
||||||
|
:status="currentUser.availability_status"
|
||||||
|
/>
|
||||||
<div class="current-user--data">
|
<div class="current-user--data">
|
||||||
<h3 class="current-user--name">
|
<h3 class="current-user--name">
|
||||||
{{ currentUser.name }}
|
{{ currentUser.name }}
|
||||||
|
@ -108,7 +105,6 @@ import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import adminMixin from '../../mixins/isAdmin';
|
import adminMixin from '../../mixins/isAdmin';
|
||||||
import Auth from '../../api/auth';
|
import Auth from '../../api/auth';
|
||||||
import SidebarItem from './SidebarItem';
|
import SidebarItem from './SidebarItem';
|
||||||
import WootStatusBar from '../widgets/StatusBar';
|
|
||||||
import { frontendURL } from '../../helper/URLHelper';
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
import Thumbnail from '../widgets/Thumbnail';
|
import Thumbnail from '../widgets/Thumbnail';
|
||||||
import { getSidebarItems } from '../../i18n/default-sidebar';
|
import { getSidebarItems } from '../../i18n/default-sidebar';
|
||||||
|
@ -116,7 +112,6 @@ import { getSidebarItems } from '../../i18n/default-sidebar';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
WootStatusBar,
|
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
},
|
},
|
||||||
mixins: [clickaway, adminMixin],
|
mixins: [clickaway, adminMixin],
|
||||||
|
@ -135,12 +130,11 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentUser: 'getCurrentUser',
|
currentUser: 'getCurrentUser',
|
||||||
daysLeft: 'getTrialLeft',
|
|
||||||
globalConfig: 'globalConfig/get',
|
globalConfig: 'globalConfig/get',
|
||||||
inboxes: 'inboxes/getInboxes',
|
inboxes: 'inboxes/getInboxes',
|
||||||
subscriptionData: 'getSubscription',
|
|
||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
currentRole: 'getCurrentRole',
|
currentRole: 'getCurrentRole',
|
||||||
|
accountLabels: 'labels/getLabelsOnSidebar',
|
||||||
}),
|
}),
|
||||||
sidemenuItems() {
|
sidemenuItems() {
|
||||||
return getSidebarItems(this.accountId);
|
return getSidebarItems(this.accountId);
|
||||||
|
@ -160,10 +154,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.chatwootConfig.billingEnabled) {
|
|
||||||
menuItems = this.filterBillingRoutes(menuItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.filterMenuItemsByRole(menuItems);
|
return this.filterMenuItemsByRole(menuItems);
|
||||||
},
|
},
|
||||||
currentRoute() {
|
currentRoute() {
|
||||||
|
@ -190,38 +180,33 @@ export default {
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
labelSection() {
|
||||||
|
return {
|
||||||
|
icon: 'ion-pound',
|
||||||
|
label: 'LABELS',
|
||||||
|
hasSubMenu: true,
|
||||||
|
key: 'label',
|
||||||
|
cssClass: 'menu-title align-justify',
|
||||||
|
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
|
||||||
|
toStateName: 'labels_list',
|
||||||
|
children: this.accountLabels.map(label => ({
|
||||||
|
id: label.id,
|
||||||
|
label: label.title,
|
||||||
|
color: label.color,
|
||||||
|
toState: frontendURL(
|
||||||
|
`accounts/${this.accountId}/label/${label.title}`
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
dashboardPath() {
|
dashboardPath() {
|
||||||
return frontendURL(`accounts/${this.accountId}/dashboard`);
|
return frontendURL(`accounts/${this.accountId}/dashboard`);
|
||||||
},
|
},
|
||||||
shouldShowStatusBox() {
|
|
||||||
return (
|
|
||||||
window.chatwootConfig.billingEnabled &&
|
|
||||||
(this.subscriptionData.state === 'trial' ||
|
|
||||||
this.subscriptionData.state === 'cancelled')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
statusBarClass() {
|
|
||||||
if (this.subscriptionData.state === 'trial') {
|
|
||||||
return 'warning';
|
|
||||||
}
|
|
||||||
if (this.subscriptionData.state === 'cancelled') {
|
|
||||||
return 'danger';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
trialMessage() {
|
|
||||||
return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('inboxes/get');
|
this.$store.dispatch('inboxes/get');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
filterBillingRoutes(menuItems) {
|
|
||||||
return menuItems.filter(
|
|
||||||
menuItem => !menuItem.toState.includes('billing')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
filterMenuItemsByRole(menuItems) {
|
filterMenuItemsByRole(menuItems) {
|
||||||
if (!this.currentRole) {
|
if (!this.currentRole) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -36,7 +36,13 @@
|
||||||
v-if="computedInboxClass(child)"
|
v-if="computedInboxClass(child)"
|
||||||
class="inbox-icon"
|
class="inbox-icon"
|
||||||
:class="computedInboxClass(child)"
|
:class="computedInboxClass(child)"
|
||||||
></i>
|
/>
|
||||||
|
<span
|
||||||
|
v-if="child.color"
|
||||||
|
class="label-color--display"
|
||||||
|
:style="{ backgroundColor: child.color }"
|
||||||
|
/>
|
||||||
|
|
||||||
{{ child.label }}
|
{{ child.label }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -126,8 +132,22 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import '~dashboard/assets/scss/variables';
|
||||||
|
|
||||||
.sub-menu-title {
|
.sub-menu-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-color--display {
|
||||||
|
border-radius: $space-smaller;
|
||||||
|
height: $space-normal;
|
||||||
|
margin-right: $space-small;
|
||||||
|
width: $space-normal;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="back-button ion-ios-arrow-left" @click.capture="goBack">Back</span>
|
<span class="back-button ion-ios-arrow-left" @click.capture="goBack">
|
||||||
|
{{ $t('GENERAL_SETTINGS.BACK') }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import router from '../../routes/index';
|
import router from '../../routes/index';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
backUrl: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goBack() {
|
goBack() {
|
||||||
|
if (this.backUrl !== '') {
|
||||||
|
router.push(this.backUrl);
|
||||||
|
} else {
|
||||||
router.go(-1);
|
router.go(-1);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,10 +40,23 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
enabledFeatures: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isActive(channel) {
|
isActive(channel) {
|
||||||
return ['facebook', 'website', 'twitter', 'twilio'].includes(channel);
|
if (Object.keys(this.enabledFeatures) === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (channel === 'facebook') {
|
||||||
|
return this.enabledFeatures.channel_facebook;
|
||||||
|
}
|
||||||
|
if (channel === 'twitter') {
|
||||||
|
return this.enabledFeatures.channel_facebook;
|
||||||
|
}
|
||||||
|
return ['website', 'twilio'].includes(channel);
|
||||||
},
|
},
|
||||||
onItemClick() {
|
onItemClick() {
|
||||||
if (this.isActive(this.channel)) {
|
if (this.isActive(this.channel)) {
|
||||||
|
|
|
@ -22,11 +22,6 @@ export default {
|
||||||
default: wootConstants.ASSIGNEE_TYPE.ME,
|
default: wootConstants.ASSIGNEE_TYPE.ME,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tabsIndex: wootConstants.ASSIGNEE_TYPE.ME,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
activeTabIndex() {
|
activeTabIndex() {
|
||||||
return this.items.findIndex(item => item.key === this.activeTab);
|
return this.items.findIndex(item => item.key === this.activeTab);
|
||||||
|
|
91
app/javascript/dashboard/components/widgets/Label.vue
Normal file
91
app/javascript/dashboard/components/widgets/Label.vue
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="labelClass"
|
||||||
|
:style="{ background: bgColor, color: textColor }"
|
||||||
|
:title="description"
|
||||||
|
>
|
||||||
|
<span v-if="!href">{{ title }}</span>
|
||||||
|
<a v-else :href="href" :style="{ color: textColor }">{{ title }}</a>
|
||||||
|
<i v-if="showIcon" class="label--icon" :class="icon" @click="onClick" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#1f93ff',
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showIcon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'ion-close',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
textColor() {
|
||||||
|
const color = this.bgColor.replace('#', '');
|
||||||
|
const r = parseInt(color.slice(0, 2), 16);
|
||||||
|
const g = parseInt(color.slice(2, 4), 16);
|
||||||
|
const b = parseInt(color.slice(4, 6), 16);
|
||||||
|
// http://stackoverflow.com/a/3943023/112731
|
||||||
|
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#FFFFFF';
|
||||||
|
},
|
||||||
|
labelClass() {
|
||||||
|
return `label ${this.small ? 'small' : ''}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.$emit('click', this.title);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~dashboard/assets/scss/variables';
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
line-height: 1;
|
||||||
|
margin: $space-micro;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
font-size: $font-size-mini;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label--icon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $font-size-micro;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-left: $space-smaller;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,24 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="status-bar" :class="type">
|
|
||||||
<p class="message">{{message}}</p>
|
|
||||||
<router-link
|
|
||||||
:to="buttonRoute"
|
|
||||||
class="button small warning nice"
|
|
||||||
v-if="showButton"
|
|
||||||
>
|
|
||||||
{{buttonText}}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: String,
|
|
||||||
buttonRoute: Object,
|
|
||||||
buttonText: String,
|
|
||||||
showButton: Boolean,
|
|
||||||
type: String, // Danger, Info, Success, Warning
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -21,11 +21,6 @@
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="~dashboard/assets/images/fb-badge.png"
|
src="~dashboard/assets/images/fb-badge.png"
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
v-else-if="status === 'online'"
|
|
||||||
class="source-badge user--online"
|
|
||||||
:style="statusStyle"
|
|
||||||
></div>
|
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'Channel::TwitterProfile'"
|
v-if="badge === 'Channel::TwitterProfile'"
|
||||||
id="badge"
|
id="badge"
|
||||||
|
@ -33,7 +28,6 @@
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="~dashboard/assets/images/twitter-badge.png"
|
src="~dashboard/assets/images/twitter-badge.png"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'Channel::TwilioSms'"
|
v-if="badge === 'Channel::TwilioSms'"
|
||||||
id="badge"
|
id="badge"
|
||||||
|
@ -41,6 +35,11 @@
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="~dashboard/assets/images/channels/whatsapp.png"
|
src="~dashboard/assets/images/channels/whatsapp.png"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="showStatusIndicator"
|
||||||
|
:class="`source-badge user-online-status user-online-status--${status}`"
|
||||||
|
:style="statusStyle"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -89,6 +88,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showStatusIndicator() {
|
||||||
|
return this.status === 'online' || this.status === 'busy';
|
||||||
|
},
|
||||||
avatarSize() {
|
avatarSize() {
|
||||||
return Number(this.size.replace(/\D+/g, ''));
|
return Number(this.size.replace(/\D+/g, ''));
|
||||||
},
|
},
|
||||||
|
@ -150,8 +152,7 @@ export default {
|
||||||
width: $space-slab;
|
width: $space-slab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user--online {
|
.user-online-status {
|
||||||
background: $success-color;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
bottom: $space-micro;
|
bottom: $space-micro;
|
||||||
|
|
||||||
|
@ -159,5 +160,13 @@ export default {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-online-status--online {
|
||||||
|
background: $success-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-online-status--busy {
|
||||||
|
background: $warning-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
v-if="!hideThumbnail"
|
v-if="!hideThumbnail"
|
||||||
:src="currentContact.thumbnail"
|
:src="currentContact.thumbnail"
|
||||||
:badge="currentContact.channel"
|
:badge="chatMetadata.channel"
|
||||||
class="columns"
|
class="columns"
|
||||||
:username="currentContact.name"
|
:username="currentContact.name"
|
||||||
|
:status="currentContact.availability_status"
|
||||||
size="40px"
|
size="40px"
|
||||||
/>
|
/>
|
||||||
<div class="conversation--details columns">
|
<div class="conversation--details columns">
|
||||||
|
@ -23,15 +24,20 @@
|
||||||
{{ inboxName(chat.inbox_id) }}
|
{{ inboxName(chat.inbox_id) }}
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
<p
|
<p v-if="lastMessageInChat" class="conversation--message">
|
||||||
class="conversation--message"
|
<i v-if="messageByAgent" class="ion-ios-undo message-from-agent"></i>
|
||||||
v-html="extractMessageText(lastMessageInChat)"
|
<span v-if="lastMessageInChat.content">
|
||||||
/>
|
{{ lastMessageInChat.content }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="!lastMessageInChat.attachments">{{ ` ` }}</span>
|
||||||
|
<span v-else>
|
||||||
|
<i :class="`small-icon ${this.$t(`${attachmentIconKey}.ICON`)}`"></i>
|
||||||
|
{{ this.$t(`${attachmentIconKey}.CONTENT`) }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
<div class="conversation--meta">
|
<div class="conversation--meta">
|
||||||
<span class="timestamp">
|
<span class="timestamp">
|
||||||
{{
|
{{ dynamicTime(chat.timestamp) }}
|
||||||
lastMessageInChat ? dynamicTime(lastMessageInChat.created_at) : ''
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="unread">{{ getUnreadCount }}</span>
|
<span class="unread">{{ getUnreadCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,11 +45,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
/* eslint no-console: 0 */
|
|
||||||
/* eslint no-extra-boolean-cast: 0 */
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
|
||||||
import Thumbnail from '../Thumbnail';
|
import Thumbnail from '../Thumbnail';
|
||||||
import getEmojiSVG from '../emoji/utils';
|
|
||||||
import conversationMixin from '../../../mixins/conversations';
|
import conversationMixin from '../../../mixins/conversations';
|
||||||
import timeMixin from '../../../mixins/time';
|
import timeMixin from '../../../mixins/time';
|
||||||
import router from '../../../routes';
|
import router from '../../../routes';
|
||||||
|
@ -56,6 +61,10 @@ export default {
|
||||||
|
|
||||||
mixins: [timeMixin, conversationMixin],
|
mixins: [timeMixin, conversationMixin],
|
||||||
props: {
|
props: {
|
||||||
|
activeLabel: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
chat: {
|
chat: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
|
@ -79,12 +88,22 @@ export default {
|
||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
chatMetadata() {
|
||||||
|
return this.chat.meta;
|
||||||
|
},
|
||||||
|
|
||||||
currentContact() {
|
currentContact() {
|
||||||
return this.$store.getters['contacts/getContact'](
|
return this.$store.getters['contacts/getContact'](
|
||||||
this.chat.meta.sender.id
|
this.chatMetadata.sender.id
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
attachmentIconKey() {
|
||||||
|
const lastMessage = this.lastMessageInChat;
|
||||||
|
const [{ file_type: fileType } = {}] = lastMessage.attachments;
|
||||||
|
return `CHAT_LIST.ATTACHMENTS.${fileType}`;
|
||||||
|
},
|
||||||
|
|
||||||
isActiveChat() {
|
isActiveChat() {
|
||||||
return this.currentChat.id === this.chat.id;
|
return this.currentChat.id === this.chat.id;
|
||||||
},
|
},
|
||||||
|
@ -104,38 +123,25 @@ export default {
|
||||||
lastMessageInChat() {
|
lastMessageInChat() {
|
||||||
return this.lastMessage(this.chat);
|
return this.lastMessage(this.chat);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
messageByAgent() {
|
||||||
|
const lastMessage = this.lastMessageInChat;
|
||||||
|
const { message_type: messageType } = lastMessage;
|
||||||
|
return messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
cardClick(chat) {
|
cardClick(chat) {
|
||||||
const { activeInbox } = this;
|
const { activeInbox } = this;
|
||||||
const path = conversationUrl(this.accountId, activeInbox, chat.id);
|
const path = conversationUrl({
|
||||||
|
accountId: this.accountId,
|
||||||
|
activeInbox,
|
||||||
|
id: chat.id,
|
||||||
|
label: this.activeLabel,
|
||||||
|
});
|
||||||
router.push({ path: frontendURL(path) });
|
router.push({ path: frontendURL(path) });
|
||||||
},
|
},
|
||||||
extractMessageText(chatItem) {
|
|
||||||
if (!chatItem) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { content, attachments } = chatItem;
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
if (!attachments) {
|
|
||||||
return ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
const [attachment] = attachments;
|
|
||||||
const { file_type: fileType } = attachment;
|
|
||||||
const key = `CHAT_LIST.ATTACHMENTS.${fileType}`;
|
|
||||||
return `
|
|
||||||
<i class="small-icon ${this.$t(`${key}.ICON`)}"></i>
|
|
||||||
${this.$t(`${key}.CONTENT`)}
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
getEmojiSVG,
|
|
||||||
|
|
||||||
inboxName(inboxId) {
|
inboxName(inboxId) {
|
||||||
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
||||||
return stateInbox.name || '';
|
return stateInbox.name || '';
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
:src="currentContact.thumbnail"
|
:src="currentContact.thumbnail"
|
||||||
size="40px"
|
size="40px"
|
||||||
:badge="currentContact.channel"
|
:badge="chatMetadata.channel"
|
||||||
:username="currentContact.name"
|
:username="currentContact.name"
|
||||||
|
:status="currentContact.availability_status"
|
||||||
/>
|
/>
|
||||||
<div class="user--profile__meta">
|
<div class="user--profile__meta">
|
||||||
<h3 v-if="!isContactPanelOpen" class="user--name text-truncate">
|
<h3 v-if="!isContactPanelOpen" class="user--name text-truncate">
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
deselect-label="Remove"
|
deselect-label="Remove"
|
||||||
placeholder="Select Agent"
|
placeholder="Select Agent"
|
||||||
selected-label=""
|
selected-label
|
||||||
select-label="Assign"
|
select-label="Assign"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
@select="assignAgent"
|
@select="assignAgent"
|
||||||
|
@ -80,6 +81,10 @@ export default {
|
||||||
currentChat: 'getSelectedChat',
|
currentChat: 'getSelectedChat',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
chatMetadata() {
|
||||||
|
return this.chat.meta;
|
||||||
|
},
|
||||||
|
|
||||||
currentContact() {
|
currentContact() {
|
||||||
return this.$store.getters['contacts/getContact'](
|
return this.$store.getters['contacts/getContact'](
|
||||||
this.chat.meta.sender.id
|
this.chat.meta.sender.id
|
||||||
|
|
|
@ -121,23 +121,6 @@ export default {
|
||||||
);
|
);
|
||||||
return chat;
|
return chat;
|
||||||
},
|
},
|
||||||
// Get current FB Page ID
|
|
||||||
getPageId() {
|
|
||||||
let stateInbox;
|
|
||||||
if (this.inboxId) {
|
|
||||||
const inboxId = Number(this.inboxId);
|
|
||||||
[stateInbox] = this.inboxesList.filter(
|
|
||||||
inbox => inbox.channel_id === inboxId
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
[stateInbox] = this.inboxesList;
|
|
||||||
}
|
|
||||||
return !stateInbox ? 0 : stateInbox.page_id;
|
|
||||||
},
|
|
||||||
// Get current FB Page ID link
|
|
||||||
linkToMessage() {
|
|
||||||
return `https://m.me/${this.getPageId}`;
|
|
||||||
},
|
|
||||||
getReadMessages() {
|
getReadMessages() {
|
||||||
const chat = this.getMessages;
|
const chat = this.getMessages;
|
||||||
return chat === undefined ? null : this.readMessages(chat);
|
return chat === undefined ? null : this.readMessages(chat);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="reply-box">
|
<div class="reply-box" :class="replyBoxClass">
|
||||||
<div class="reply-box__top" :class="{ 'is-private': isPrivate }">
|
<div class="reply-box__top" :class="{ 'is-private': isPrivate }">
|
||||||
<canned-response
|
<canned-response
|
||||||
v-if="showCannedResponsesList"
|
v-if="showCannedResponsesList"
|
||||||
|
@ -13,13 +13,12 @@
|
||||||
v-on-clickaway="hideEmojiPicker"
|
v-on-clickaway="hideEmojiPicker"
|
||||||
:on-click="emojiOnClick"
|
:on-click="emojiOnClick"
|
||||||
/>
|
/>
|
||||||
<textarea
|
<resizable-text-area
|
||||||
ref="messageInput"
|
ref="messageInput"
|
||||||
v-model="message"
|
v-model="message"
|
||||||
rows="1"
|
|
||||||
class="input"
|
class="input"
|
||||||
type="text"
|
|
||||||
:placeholder="$t(messagePlaceHolder())"
|
:placeholder="$t(messagePlaceHolder())"
|
||||||
|
:min-height="4"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
/>
|
/>
|
||||||
|
@ -93,18 +92,21 @@ import FileUpload from 'vue-upload-component';
|
||||||
|
|
||||||
import EmojiInput from '../emoji/EmojiInput';
|
import EmojiInput from '../emoji/EmojiInput';
|
||||||
import CannedResponse from './CannedResponse';
|
import CannedResponse from './CannedResponse';
|
||||||
|
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
EmojiInput,
|
EmojiInput,
|
||||||
CannedResponse,
|
CannedResponse,
|
||||||
FileUpload,
|
FileUpload,
|
||||||
|
ResizableTextArea,
|
||||||
},
|
},
|
||||||
mixins: [clickaway],
|
mixins: [clickaway],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
message: '',
|
message: '',
|
||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
|
isFocused: false,
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
showCannedResponsesList: false,
|
showCannedResponsesList: false,
|
||||||
isUploading: {
|
isUploading: {
|
||||||
|
@ -119,12 +121,7 @@ export default {
|
||||||
currentChat: 'getSelectedChat',
|
currentChat: 'getSelectedChat',
|
||||||
}),
|
}),
|
||||||
channelType() {
|
channelType() {
|
||||||
const {
|
return this.currentChat.meta.channel;
|
||||||
meta: {
|
|
||||||
sender: { channel },
|
|
||||||
},
|
|
||||||
} = this.currentChat;
|
|
||||||
return channel;
|
|
||||||
},
|
},
|
||||||
conversationType() {
|
conversationType() {
|
||||||
const { additional_attributes: additionalAttributes } = this.currentChat;
|
const { additional_attributes: additionalAttributes } = this.currentChat;
|
||||||
|
@ -143,10 +140,7 @@ export default {
|
||||||
return 10000;
|
return 10000;
|
||||||
},
|
},
|
||||||
showFileUpload() {
|
showFileUpload() {
|
||||||
return (
|
return this.channelType === 'Channel::WebWidget';
|
||||||
this.channelType === 'Channel::WebWidget' ||
|
|
||||||
this.channelType === 'Channel::TwilioSms'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
replyButtonLabel() {
|
replyButtonLabel() {
|
||||||
if (this.isPrivate) {
|
if (this.isPrivate) {
|
||||||
|
@ -157,6 +151,11 @@ export default {
|
||||||
}
|
}
|
||||||
return this.$t('CONVERSATION.REPLYBOX.SEND');
|
return this.$t('CONVERSATION.REPLYBOX.SEND');
|
||||||
},
|
},
|
||||||
|
replyBoxClass() {
|
||||||
|
return {
|
||||||
|
'is-focused': this.isFocused,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
message(val) {
|
message(val) {
|
||||||
|
@ -211,18 +210,19 @@ export default {
|
||||||
if (this.message.length > this.maxLength) {
|
if (this.message.length > this.maxLength) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const newMessage = this.message;
|
||||||
if (!this.showCannedResponsesList) {
|
if (!this.showCannedResponsesList) {
|
||||||
|
this.clearMessage();
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('sendMessage', {
|
await this.$store.dispatch('sendMessage', {
|
||||||
conversationId: this.currentChat.id,
|
conversationId: this.currentChat.id,
|
||||||
message: this.message,
|
message: newMessage,
|
||||||
private: this.isPrivate,
|
private: this.isPrivate,
|
||||||
});
|
});
|
||||||
this.$emit('scrollToMessage');
|
this.$emit('scrollToMessage');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Error
|
// Error
|
||||||
}
|
}
|
||||||
this.clearMessage();
|
|
||||||
this.hideEmojiPicker();
|
this.hideEmojiPicker();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -260,16 +260,18 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlur() {
|
onBlur() {
|
||||||
|
this.isFocused = false;
|
||||||
this.toggleTyping('off');
|
this.toggleTyping('off');
|
||||||
},
|
},
|
||||||
onFocus() {
|
onFocus() {
|
||||||
|
this.isFocused = true;
|
||||||
this.toggleTyping('on');
|
this.toggleTyping('on');
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleTyping(status) {
|
toggleTyping(status) {
|
||||||
if (this.channelType === 'Channel::WebWidget' && !this.isPrivate) {
|
if (this.channelType === 'Channel::WebWidget' && !this.isPrivate) {
|
||||||
const conversationId = this.currentChat.id;
|
const conversationId = this.currentChat.id;
|
||||||
this.$store.dispatch('toggleTyping', {
|
this.$store.dispatch('conversationTypingStatus/toggleTyping', {
|
||||||
status,
|
status,
|
||||||
conversationId,
|
conversationId,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
/* eslint no-console: 0 */
|
/* eslint no-console: 0 */
|
||||||
import constants from '../constants';
|
import constants from '../constants';
|
||||||
import Auth from '../api/auth';
|
import Auth from '../api/auth';
|
||||||
import router from '../routes';
|
|
||||||
|
|
||||||
const parseErrorCode = error => {
|
const parseErrorCode = error => {
|
||||||
const errorStatus = error.response ? error.response.status : undefined;
|
|
||||||
// 901, 902 are used to identify billing related issues
|
|
||||||
if ([901, 902].includes(errorStatus)) {
|
|
||||||
const name = Auth.isAdmin() ? 'billing' : 'billing_deactivated';
|
|
||||||
router.push({ name });
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,14 @@ export const frontendURL = (path, params) => {
|
||||||
return `/app/${path}${stringifiedParams}`;
|
return `/app/${path}${stringifiedParams}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const conversationUrl = (accountId, activeInbox, id) => {
|
export const conversationUrl = ({ accountId, activeInbox, id, label }) => {
|
||||||
const path = activeInbox
|
if (activeInbox) {
|
||||||
? `accounts/${accountId}/inbox/${activeInbox}/conversations/${id}`
|
return `accounts/${accountId}/inbox/${activeInbox}/conversations/${id}`;
|
||||||
: `accounts/${accountId}/conversations/${id}`;
|
}
|
||||||
return path;
|
if (label) {
|
||||||
|
return `accounts/${accountId}/label/${label}/conversations/${id}`;
|
||||||
|
}
|
||||||
|
return `accounts/${accountId}/conversations/${id}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accountIdFromPathname = pathname => {
|
export const accountIdFromPathname = pathname => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import AuthAPI from '../api/auth';
|
import AuthAPI from '../api/auth';
|
||||||
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
|
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
|
||||||
|
/* global bus */
|
||||||
|
|
||||||
class ActionCableConnector extends BaseActionCableConnector {
|
class ActionCableConnector extends BaseActionCableConnector {
|
||||||
constructor(app, pubsubToken) {
|
constructor(app, pubsubToken) {
|
||||||
|
@ -17,6 +18,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
'conversation.typing_on': this.onTypingOn,
|
'conversation.typing_on': this.onTypingOn,
|
||||||
'conversation.typing_off': this.onTypingOff,
|
'conversation.typing_off': this.onTypingOff,
|
||||||
'conversation.contact_changed': this.onConversationContactChange,
|
'conversation.contact_changed': this.onConversationContactChange,
|
||||||
|
'presence.update': this.onPresenceUpdate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +30,12 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
this.app.$store.dispatch('updateMessage', data);
|
this.app.$store.dispatch('updateMessage', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onPresenceUpdate = data => {
|
||||||
|
this.app.$store.dispatch('contacts/updatePresence', data.contacts);
|
||||||
|
this.app.$store.dispatch('agents/updatePresence', data.users);
|
||||||
|
this.app.$store.dispatch('setCurrentUserAvailabilityStatus', data.users);
|
||||||
|
};
|
||||||
|
|
||||||
onConversationContactChange = payload => {
|
onConversationContactChange = payload => {
|
||||||
const { meta = {}, id: conversationId } = payload;
|
const { meta = {}, id: conversationId } = payload;
|
||||||
const { sender } = meta || {};
|
const { sender } = meta || {};
|
||||||
|
@ -45,10 +53,12 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
if (id) {
|
if (id) {
|
||||||
this.app.$store.dispatch('updateAssignee', { id, assignee });
|
this.app.$store.dispatch('updateAssignee', { id, assignee });
|
||||||
}
|
}
|
||||||
|
this.fetchConversationStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
onConversationCreated = data => {
|
onConversationCreated = data => {
|
||||||
this.app.$store.dispatch('addConversation', data);
|
this.app.$store.dispatch('addConversation', data);
|
||||||
|
this.fetchConversationStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
onLogout = () => AuthAPI.logout();
|
onLogout = () => AuthAPI.logout();
|
||||||
|
@ -61,6 +71,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
|
|
||||||
onStatusChange = data => {
|
onStatusChange = data => {
|
||||||
this.app.$store.dispatch('updateConversation', data);
|
this.app.$store.dispatch('updateConversation', data);
|
||||||
|
this.fetchConversationStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
onTypingOn = ({ conversation, user }) => {
|
onTypingOn = ({ conversation, user }) => {
|
||||||
|
@ -100,6 +111,10 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
this.onTypingOff({ conversation, user });
|
this.onTypingOff({ conversation, user });
|
||||||
}, 30000);
|
}, 30000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fetchConversationStats = () => {
|
||||||
|
bus.$emit('fetch_conversation_stats');
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -7,15 +7,20 @@ import {
|
||||||
describe('#URL Helpers', () => {
|
describe('#URL Helpers', () => {
|
||||||
describe('conversationUrl', () => {
|
describe('conversationUrl', () => {
|
||||||
it('should return direct conversation URL if activeInbox is nil', () => {
|
it('should return direct conversation URL if activeInbox is nil', () => {
|
||||||
expect(conversationUrl(1, undefined, 1)).toBe(
|
expect(conversationUrl({ accountId: 1, id: 1 })).toBe(
|
||||||
'accounts/1/conversations/1'
|
'accounts/1/conversations/1'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should return ibox conversation URL if activeInbox is not nil', () => {
|
it('should return inbox conversation URL if activeInbox is not nil', () => {
|
||||||
expect(conversationUrl(1, 2, 1)).toBe(
|
expect(conversationUrl({ accountId: 1, id: 1, activeInbox: 2 })).toBe(
|
||||||
'accounts/1/inbox/2/conversations/1'
|
'accounts/1/inbox/2/conversations/1'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('should return correct conversation URL if label is active', () => {
|
||||||
|
expect(
|
||||||
|
conversationUrl({ accountId: 1, label: 'customer-support', id: 1 })
|
||||||
|
).toBe('accounts/1/label/customer-support/conversations/1');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('frontendURL', () => {
|
describe('frontendURL', () => {
|
||||||
|
@ -27,16 +32,6 @@ describe('#URL Helpers', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
export const accountIdFromPathname = pathname => {
|
|
||||||
const isInsideAccountScopedURLs = pathname.includes('/app/accounts');
|
|
||||||
const accountId = isInsideAccountScopedURLs ? pathname.split('/')[3] : '';
|
|
||||||
return Number(accountId);
|
|
||||||
};
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('accountIdFromPathname', () => {
|
describe('accountIdFromPathname', () => {
|
||||||
it('should return account id if accont scoped url is passed', () => {
|
it('should return account id if accont scoped url is passed', () => {
|
||||||
expect(accountIdFromPathname('/app/accounts/1/settings/general')).toBe(1);
|
expect(accountIdFromPathname('/app/accounts/1/settings/general')).toBe(1);
|
||||||
|
|
|
@ -8,9 +8,10 @@ export const getSidebarItems = accountId => ({
|
||||||
'inbox_conversation',
|
'inbox_conversation',
|
||||||
'conversation_through_inbox',
|
'conversation_through_inbox',
|
||||||
'settings_account_reports',
|
'settings_account_reports',
|
||||||
'billing_deactivated',
|
|
||||||
'profile_settings',
|
'profile_settings',
|
||||||
'profile_settings_index',
|
'profile_settings_index',
|
||||||
|
'label_conversations',
|
||||||
|
'conversations_through_label',
|
||||||
],
|
],
|
||||||
menuItems: {
|
menuItems: {
|
||||||
assignedToMe: {
|
assignedToMe: {
|
||||||
|
@ -41,9 +42,8 @@ export const getSidebarItems = accountId => ({
|
||||||
settings: {
|
settings: {
|
||||||
routes: [
|
routes: [
|
||||||
'agent_list',
|
'agent_list',
|
||||||
'agent_new',
|
|
||||||
'canned_list',
|
'canned_list',
|
||||||
'canned_new',
|
'labels_list',
|
||||||
'settings_inbox',
|
'settings_inbox',
|
||||||
'settings_inbox_new',
|
'settings_inbox_new',
|
||||||
'settings_inbox_list',
|
'settings_inbox_list',
|
||||||
|
@ -51,9 +51,9 @@ export const getSidebarItems = accountId => ({
|
||||||
'settings_inboxes_page_channel',
|
'settings_inboxes_page_channel',
|
||||||
'settings_inboxes_add_agents',
|
'settings_inboxes_add_agents',
|
||||||
'settings_inbox_finish',
|
'settings_inbox_finish',
|
||||||
'billing',
|
|
||||||
'settings_integrations',
|
'settings_integrations',
|
||||||
'settings_integrations_webhook',
|
'settings_integrations_webhook',
|
||||||
|
'settings_integrations_integration',
|
||||||
'general_settings',
|
'general_settings',
|
||||||
'general_settings_index',
|
'general_settings_index',
|
||||||
],
|
],
|
||||||
|
@ -79,6 +79,13 @@ export const getSidebarItems = accountId => ({
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
|
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
|
||||||
toStateName: 'settings_inbox_list',
|
toStateName: 'settings_inbox_list',
|
||||||
},
|
},
|
||||||
|
labels: {
|
||||||
|
icon: 'ion-pricetags',
|
||||||
|
label: 'LABELS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
|
||||||
|
toStateName: 'labels_list',
|
||||||
|
},
|
||||||
cannedResponses: {
|
cannedResponses: {
|
||||||
icon: 'ion-chatbox-working',
|
icon: 'ion-chatbox-working',
|
||||||
label: 'CANNED_RESPONSES',
|
label: 'CANNED_RESPONSES',
|
||||||
|
@ -88,13 +95,6 @@ export const getSidebarItems = accountId => ({
|
||||||
),
|
),
|
||||||
toStateName: 'canned_list',
|
toStateName: 'canned_list',
|
||||||
},
|
},
|
||||||
billing: {
|
|
||||||
icon: 'ion-card',
|
|
||||||
label: 'BILLING',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/billing`),
|
|
||||||
toStateName: 'billing',
|
|
||||||
},
|
|
||||||
settings_integrations: {
|
settings_integrations: {
|
||||||
icon: 'ion-flash',
|
icon: 'ion-flash',
|
||||||
label: 'INTEGRATIONS',
|
label: 'INTEGRATIONS',
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue