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