Compare commits
143
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72a18a98bf | ||
|
|
71837bedf9 | ||
|
|
3c4514c9f7 | ||
|
|
4c7c1cd78a | ||
|
|
985b3f3bfc | ||
|
|
986abde531 | ||
|
|
cbae8358be | ||
|
|
b57063a8b8 | ||
|
|
3b4d03d155 | ||
|
|
c19b34eaaa | ||
|
|
022f4f899f | ||
|
|
191b8a64fe | ||
|
|
ea78020f2f | ||
|
|
dc1a41b136 | ||
|
|
28e7a5d228 | ||
|
|
40830046e8 | ||
|
|
d05c953eef | ||
|
|
4a770fdea7 | ||
|
|
239b1e2bf1 | ||
|
|
f963e00731 | ||
|
|
155a5b9947 | ||
|
|
26a75b250d | ||
|
|
20b84ee6a1 | ||
|
|
f1f14312fe | ||
|
|
212d7caab1 | ||
|
|
9cda16d0e8 | ||
|
|
a8acbb7427 | ||
|
|
1176e5eb8a | ||
|
|
996325f35b | ||
|
|
4f8ce7b597 | ||
|
|
f8e631a00c | ||
|
|
54bf33083e | ||
|
|
533f9f7fe4 | ||
|
|
82e5fc413e | ||
|
|
93daaea19b | ||
|
|
e6a49b5800 | ||
|
|
595e6e79f0 | ||
|
|
93d8157a55 | ||
|
|
6bea2cbc4a | ||
|
|
afb7e67795 | ||
|
|
bc813b71dd | ||
|
|
9ac4a1eb30 | ||
|
|
9bb455ec2d | ||
|
|
c410fe333a | ||
|
|
5e807c5875 | ||
|
|
9bed57c7d1 | ||
|
|
5c5381c0a9 | ||
|
|
138630b4ae | ||
|
|
9d0de04f7c | ||
|
|
2c3337b117 | ||
|
|
237358af24 | ||
|
|
35dfff0a5b | ||
|
|
86b2896333 | ||
|
|
e8a27bea4b | ||
|
|
0d465362ac | ||
|
|
e383e9259b | ||
|
|
2c34dca347 | ||
|
|
69b9123963 | ||
|
|
cac9fe1880 | ||
|
|
06ac7c829b | ||
|
|
9985f1c143 | ||
|
|
1ee6a8fe90 | ||
|
|
6eb4fa41ff | ||
|
|
8b91f0ffac | ||
|
|
d2aa19579e | ||
|
|
2e79a32db7 | ||
|
|
a86e236d19 | ||
|
|
c755978352 | ||
|
|
23ca6d56f9 | ||
|
|
429ec7194f | ||
|
|
c99d9f9557 | ||
|
|
9ff5978d6e | ||
|
|
cd28f401ba | ||
|
|
879a244f93 | ||
|
|
f64f2138db | ||
|
|
48f2e58e59 | ||
|
|
c715e396f0 | ||
|
|
c8fac08e0b | ||
|
|
7fd220c177 | ||
|
|
b3b76d00ff | ||
|
|
2f2ae88cba | ||
|
|
2b7989f52b | ||
|
|
d7314079c9 | ||
|
|
1017903ee1 | ||
|
|
d25e7fd54e | ||
|
|
7e89e3a34a | ||
|
|
39eaed7766 | ||
|
|
b1a4907ede | ||
|
|
a01d81a7e1 | ||
|
|
117d5301b4 | ||
|
|
aae6081d73 | ||
|
|
d03924b846 | ||
|
|
b333d0c986 | ||
|
|
9f3d155822 | ||
|
|
9203b097d2 | ||
|
|
12cb97db5f | ||
|
|
09971fd613 | ||
|
|
ca9d108741 | ||
|
|
f28533bc47 | ||
|
|
68f82c11a5 | ||
|
|
abc27fa791 | ||
|
|
d93a8d05bc | ||
|
|
373f5f5b64 | ||
|
|
ae7df60a75 | ||
|
|
412d750b6a | ||
|
|
f1a77ba934 | ||
|
|
ffc6364690 | ||
|
|
3a7633b564 | ||
|
|
91d1d44aa9 | ||
|
|
afc97faa8b | ||
|
|
1d0930ef63 | ||
|
|
776ea6d39a | ||
|
|
2c85098698 | ||
|
|
6bd0e074dc | ||
|
|
123fc73394 | ||
|
|
88cef88d80 | ||
|
|
8e79cf72e1 | ||
|
|
bfaca851f1 | ||
|
|
5618b5ebd3 | ||
|
|
4807052c37 | ||
|
|
44b1047b90 | ||
|
|
9c6c19c3e5 | ||
|
|
2764338453 | ||
|
|
4f0fcfcbcd | ||
|
|
fdbaf98c3b | ||
|
|
03bbd048a7 | ||
|
|
d481b9fbcf | ||
|
|
68dedc37ba | ||
|
|
958c0d7946 | ||
|
|
9553329092 | ||
|
|
d01f2063d0 | ||
|
|
590e4e9c1c | ||
|
|
0e903d2365 | ||
|
|
d6ce1ceeeb | ||
|
|
e8f56d0e56 | ||
|
|
b8dae07c0f | ||
|
|
e3f4be97c0 | ||
|
|
1f4d096804 | ||
|
|
1bcc438b21 | ||
|
|
8b3d13a920 | ||
|
|
dcdd58bae5 | ||
|
|
de1fb6ba4e | ||
|
|
18c5c47836 |
@@ -157,7 +157,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Code Climate Test Coverage
|
name: Code Climate Test Coverage
|
||||||
command: |
|
command: |
|
||||||
~/tmp/cc-test-reporter format-coverage -t lcov -o coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json buildreports/lcov.info
|
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
|
||||||
|
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: coverage
|
root: coverage
|
||||||
|
|||||||
@@ -173,6 +173,9 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
|||||||
## LogRocket
|
## LogRocket
|
||||||
# LOG_ROCKET_PROJECT_ID=xxxxx/some-project
|
# LOG_ROCKET_PROJECT_ID=xxxxx/some-project
|
||||||
|
|
||||||
|
# MICROSOFT CLARITY
|
||||||
|
# MS_CLARITY_TOKEN=xxxxxxxxx
|
||||||
|
|
||||||
## Scout
|
## Scout
|
||||||
## https://scoutapm.com/docs/ruby/configuration
|
## https://scoutapm.com/docs/ruby/configuration
|
||||||
# SCOUT_KEY=YOURKEY
|
# SCOUT_KEY=YOURKEY
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# ref: https://github.com/amannn/action-semantic-pull-request
|
||||||
|
# ensure PR title is in semantic format
|
||||||
|
|
||||||
|
name: "Lint PR"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
name: Validate PR title
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: amannn/action-semantic-pull-request@v5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Run Size Limit Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
|
||||||
|
- uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
|
||||||
|
- name: yarn
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Strip enterprise code
|
||||||
|
run: |
|
||||||
|
rm -rf enterprise
|
||||||
|
rm -rf spec/enterprise
|
||||||
|
|
||||||
|
- name: Run asset compile
|
||||||
|
run: bundle exec rake assets:precompile
|
||||||
|
|
||||||
|
- name: Size Check
|
||||||
|
run: yarn run size
|
||||||
@@ -74,3 +74,5 @@ yalc.lock
|
|||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
|
|
||||||
|
/storybook-static
|
||||||
@@ -4,7 +4,7 @@ ruby '3.2.2'
|
|||||||
|
|
||||||
##-- base gems for rails --##
|
##-- base gems for rails --##
|
||||||
gem 'rack-cors', require: 'rack/cors'
|
gem 'rack-cors', require: 'rack/cors'
|
||||||
gem 'rails', '~> 7'
|
gem 'rails', '~> 7.0.5.1'
|
||||||
# Reduces boot times through caching; required in config/boot.rb
|
# Reduces boot times through caching; required in config/boot.rb
|
||||||
gem 'bootsnap', require: false
|
gem 'bootsnap', require: false
|
||||||
|
|
||||||
@@ -75,6 +75,7 @@ gem 'jwt'
|
|||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
# super admin
|
# super admin
|
||||||
gem 'administrate'
|
gem 'administrate'
|
||||||
|
gem 'administrate-field-active_storage'
|
||||||
|
|
||||||
##--- gems for pubsub service ---##
|
##--- gems for pubsub service ---##
|
||||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||||
|
|||||||
+78
-73
@@ -33,70 +33,70 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.0.4.3)
|
actioncable (7.0.5.1)
|
||||||
actionpack (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.4.3)
|
actionmailbox (7.0.5.1)
|
||||||
actionpack (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
activejob (= 7.0.4.3)
|
activejob (= 7.0.5.1)
|
||||||
activerecord (= 7.0.4.3)
|
activerecord (= 7.0.5.1)
|
||||||
activestorage (= 7.0.4.3)
|
activestorage (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.4.3)
|
actionmailer (7.0.5.1)
|
||||||
actionpack (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
actionview (= 7.0.4.3)
|
actionview (= 7.0.5.1)
|
||||||
activejob (= 7.0.4.3)
|
activejob (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (7.0.4.3)
|
actionpack (7.0.5.1)
|
||||||
actionview (= 7.0.4.3)
|
actionview (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
rack (~> 2.0, >= 2.2.0)
|
rack (~> 2.0, >= 2.2.4)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (7.0.4.3)
|
actiontext (7.0.5.1)
|
||||||
actionpack (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
activerecord (= 7.0.4.3)
|
activerecord (= 7.0.5.1)
|
||||||
activestorage (= 7.0.4.3)
|
activestorage (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.4.3)
|
actionview (7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
active_record_query_trace (1.8)
|
active_record_query_trace (1.8)
|
||||||
activejob (7.0.4.3)
|
activejob (7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.4.3)
|
activemodel (7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
activerecord (7.0.4.3)
|
activerecord (7.0.5.1)
|
||||||
activemodel (= 7.0.4.3)
|
activemodel (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
activerecord-import (1.4.1)
|
activerecord-import (1.4.1)
|
||||||
activerecord (>= 4.2)
|
activerecord (>= 4.2)
|
||||||
activestorage (7.0.4.3)
|
activestorage (7.0.5.1)
|
||||||
actionpack (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
activejob (= 7.0.4.3)
|
activejob (= 7.0.5.1)
|
||||||
activerecord (= 7.0.4.3)
|
activerecord (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (7.0.4.3)
|
activesupport (7.0.5.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
@@ -113,6 +113,9 @@ GEM
|
|||||||
kaminari (>= 1.0)
|
kaminari (>= 1.0)
|
||||||
sassc-rails (~> 2.1)
|
sassc-rails (~> 2.1)
|
||||||
selectize-rails (~> 0.6)
|
selectize-rails (~> 0.6)
|
||||||
|
administrate-field-active_storage (0.4.2)
|
||||||
|
administrate (>= 0.2.2)
|
||||||
|
rails (>= 7.0)
|
||||||
annotate (3.2.0)
|
annotate (3.2.0)
|
||||||
activerecord (>= 3.2, < 8.0)
|
activerecord (>= 3.2, < 8.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
@@ -353,7 +356,7 @@ GEM
|
|||||||
mini_mime (>= 1.0.0)
|
mini_mime (>= 1.0.0)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.13.0)
|
i18n (1.14.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_processing (1.12.2)
|
image_processing (1.12.2)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
@@ -420,9 +423,9 @@ GEM
|
|||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.20.0)
|
loofah (2.21.3)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
net-imap
|
net-imap
|
||||||
@@ -438,7 +441,7 @@ GEM
|
|||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.8.2)
|
mini_portile2 (2.8.2)
|
||||||
minitest (5.18.0)
|
minitest (5.18.1)
|
||||||
mock_redis (0.36.0)
|
mock_redis (0.36.0)
|
||||||
ruby2_keywords
|
ruby2_keywords
|
||||||
msgpack (1.7.0)
|
msgpack (1.7.0)
|
||||||
@@ -447,7 +450,7 @@ GEM
|
|||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.3.4)
|
net-imap (0.3.6)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -462,14 +465,14 @@ GEM
|
|||||||
sidekiq
|
sidekiq
|
||||||
newrelic_rpm (8.16.0)
|
newrelic_rpm (8.16.0)
|
||||||
nio4r (2.5.9)
|
nio4r (2.5.9)
|
||||||
nokogiri (1.14.3)
|
nokogiri (1.15.2)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.14.3-arm64-darwin)
|
nokogiri (1.15.2-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.14.3-x86_64-darwin)
|
nokogiri (1.15.2-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.14.3-x86_64-linux)
|
nokogiri (1.15.2-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oauth (1.1.0)
|
oauth (1.1.0)
|
||||||
oauth-tty (~> 1.0, >= 1.0.1)
|
oauth-tty (~> 1.0, >= 1.0.1)
|
||||||
@@ -522,7 +525,7 @@ GEM
|
|||||||
pundit (2.3.0)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.6.2)
|
racc (1.7.1)
|
||||||
rack (2.2.7)
|
rack (2.2.7)
|
||||||
rack-attack (6.6.1)
|
rack-attack (6.6.1)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
@@ -537,28 +540,29 @@ GEM
|
|||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rack-timeout (0.6.3)
|
rack-timeout (0.6.3)
|
||||||
rails (7.0.4.3)
|
rails (7.0.5.1)
|
||||||
actioncable (= 7.0.4.3)
|
actioncable (= 7.0.5.1)
|
||||||
actionmailbox (= 7.0.4.3)
|
actionmailbox (= 7.0.5.1)
|
||||||
actionmailer (= 7.0.4.3)
|
actionmailer (= 7.0.5.1)
|
||||||
actionpack (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
actiontext (= 7.0.4.3)
|
actiontext (= 7.0.5.1)
|
||||||
actionview (= 7.0.4.3)
|
actionview (= 7.0.5.1)
|
||||||
activejob (= 7.0.4.3)
|
activejob (= 7.0.5.1)
|
||||||
activemodel (= 7.0.4.3)
|
activemodel (= 7.0.5.1)
|
||||||
activerecord (= 7.0.4.3)
|
activerecord (= 7.0.5.1)
|
||||||
activestorage (= 7.0.4.3)
|
activestorage (= 7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
activesupport (= 7.0.5.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.4.3)
|
railties (= 7.0.5.1)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.5.0)
|
rails-html-sanitizer (1.6.0)
|
||||||
loofah (~> 2.19, >= 2.19.1)
|
loofah (~> 2.21)
|
||||||
railties (7.0.4.3)
|
nokogiri (~> 1.14)
|
||||||
actionpack (= 7.0.4.3)
|
railties (7.0.5.1)
|
||||||
activesupport (= 7.0.4.3)
|
actionpack (= 7.0.5.1)
|
||||||
|
activesupport (= 7.0.5.1)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
@@ -722,12 +726,12 @@ GEM
|
|||||||
stripe (8.5.0)
|
stripe (8.5.0)
|
||||||
telephone_number (1.4.20)
|
telephone_number (1.4.20)
|
||||||
test-prof (1.2.1)
|
test-prof (1.2.1)
|
||||||
thor (1.2.1)
|
thor (1.2.2)
|
||||||
tilt (2.1.0)
|
tilt (2.2.0)
|
||||||
time_diff (0.3.0)
|
time_diff (0.3.0)
|
||||||
activesupport
|
activesupport
|
||||||
i18n
|
i18n
|
||||||
timeout (0.3.2)
|
timeout (0.4.0)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
twilio-ruby (5.77.0)
|
twilio-ruby (5.77.0)
|
||||||
faraday (>= 0.9, < 3.0)
|
faraday (>= 0.9, < 3.0)
|
||||||
@@ -784,8 +788,8 @@ GEM
|
|||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-20
|
arm64-darwin-20
|
||||||
arm64-darwin-22
|
|
||||||
arm64-darwin-21
|
arm64-darwin-21
|
||||||
|
arm64-darwin-22
|
||||||
ruby
|
ruby
|
||||||
x86_64-darwin-18
|
x86_64-darwin-18
|
||||||
x86_64-darwin-20
|
x86_64-darwin-20
|
||||||
@@ -798,6 +802,7 @@ DEPENDENCIES
|
|||||||
activerecord-import
|
activerecord-import
|
||||||
acts-as-taggable-on
|
acts-as-taggable-on
|
||||||
administrate
|
administrate
|
||||||
|
administrate-field-active_storage
|
||||||
annotate
|
annotate
|
||||||
attr_extras
|
attr_extras
|
||||||
audited (~> 5.3)
|
audited (~> 5.3)
|
||||||
@@ -870,7 +875,7 @@ DEPENDENCIES
|
|||||||
rack-cors
|
rack-cors
|
||||||
rack-mini-profiler
|
rack-mini-profiler
|
||||||
rack-timeout
|
rack-timeout
|
||||||
rails (~> 7)
|
rails (~> 7.0.5.1)
|
||||||
redis
|
redis
|
||||||
redis-namespace
|
redis-namespace
|
||||||
responders
|
responders
|
||||||
@@ -916,4 +921,4 @@ RUBY VERSION
|
|||||||
ruby 3.2.2p185
|
ruby 3.2.2p185
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.4.10
|
2.4.6
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//= link_tree ../images
|
//= link_tree ../images
|
||||||
//= link administrate/application.css
|
//= link administrate/application.css
|
||||||
//= link administrate/application.js
|
//= link administrate/application.js
|
||||||
|
//= link administrate-field-active_storage/application.css
|
||||||
//= link dashboardChart.js
|
//= link dashboardChart.js
|
||||||
//= link secretField.js
|
//= link secretField.js
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function prepareData(data) {
|
|||||||
|
|
||||||
function getChartOptions() {
|
function getChartOptions() {
|
||||||
var fontFamily =
|
var fontFamily =
|
||||||
'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
|
'PlusJakarta,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
|
||||||
return {
|
return {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
legend: { labels: { fontFamily } },
|
legend: { labels: { fontFamily } },
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// Typography
|
// Typography
|
||||||
$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
$base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
sans-serif !default;
|
sans-serif !default;
|
||||||
$heading-font-family: $base-font-family !default;
|
$heading-font-family: $base-font-family !default;
|
||||||
|
|
||||||
$base-font-size: 10px !default;
|
$base-font-size: 16px !default;
|
||||||
|
|
||||||
$base-line-height: 1.5 !default;
|
$base-line-height: 1.5 !default;
|
||||||
$heading-line-height: 1.2 !default;
|
$heading-line-height: 1.2 !default;
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
@conversation ||= Conversation.where(
|
@conversation ||= Conversation.where(conversation_params).find_by(
|
||||||
"additional_attributes ->> 'type' = 'instagram_direct_message'"
|
"additional_attributes ->> 'type' = 'instagram_direct_message'"
|
||||||
).find_by(conversation_params) || build_conversation
|
) || build_conversation
|
||||||
end
|
end
|
||||||
|
|
||||||
def message_content
|
def message_content
|
||||||
@@ -96,6 +96,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
|||||||
|
|
||||||
def build_conversation
|
def build_conversation
|
||||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
|
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: message_source_id)
|
||||||
|
|
||||||
Conversation.create!(conversation_params.merge(
|
Conversation.create!(conversation_params.merge(
|
||||||
contact_inbox_id: @contact_inbox.id,
|
contact_inbox_id: @contact_inbox.id,
|
||||||
additional_attributes: { type: 'instagram_direct_message' }
|
additional_attributes: { type: 'instagram_direct_message' }
|
||||||
|
|||||||
@@ -48,17 +48,22 @@ class Messages::MessageBuilder
|
|||||||
def process_emails
|
def process_emails
|
||||||
return unless @conversation.inbox&.inbox_type == 'Email'
|
return unless @conversation.inbox&.inbox_type == 'Email'
|
||||||
|
|
||||||
cc_emails = []
|
cc_emails = process_email_string(@params[:cc_emails])
|
||||||
cc_emails = @params[:cc_emails].gsub(/\s+/, '').split(',') if @params[:cc_emails].present?
|
bcc_emails = process_email_string(@params[:bcc_emails])
|
||||||
|
to_emails = process_email_string(@params[:to_emails])
|
||||||
|
|
||||||
bcc_emails = []
|
all_email_addresses = cc_emails + bcc_emails + to_emails
|
||||||
bcc_emails = @params[:bcc_emails].gsub(/\s+/, '').split(',') if @params[:bcc_emails].present?
|
|
||||||
|
|
||||||
all_email_addresses = cc_emails + bcc_emails
|
|
||||||
validate_email_addresses(all_email_addresses)
|
validate_email_addresses(all_email_addresses)
|
||||||
|
|
||||||
@message.content_attributes[:cc_emails] = cc_emails
|
@message.content_attributes[:cc_emails] = cc_emails
|
||||||
@message.content_attributes[:bcc_emails] = bcc_emails
|
@message.content_attributes[:bcc_emails] = bcc_emails
|
||||||
|
@message.content_attributes[:to_emails] = to_emails
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_email_string(email_string)
|
||||||
|
return [] if email_string.blank?
|
||||||
|
|
||||||
|
email_string.gsub(/\s+/, '').split(',')
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_email_addresses(all_emails)
|
def validate_email_addresses(all_emails)
|
||||||
|
|||||||
@@ -96,10 +96,9 @@ class V2::ReportBuilder
|
|||||||
|
|
||||||
def conversations
|
def conversations
|
||||||
@open_conversations = scope.conversations.where(account_id: @account.id).open
|
@open_conversations = scope.conversations.where(account_id: @account.id).open
|
||||||
first_response_count = @account.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
|
|
||||||
metric = {
|
metric = {
|
||||||
open: @open_conversations.count,
|
open: @open_conversations.count,
|
||||||
unattended: @open_conversations.count - first_response_count
|
unattended: @open_conversations.unattended.count
|
||||||
}
|
}
|
||||||
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
|
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
|
||||||
metric
|
metric
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::Contacts::BaseController
|
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::Contacts::BaseController
|
||||||
|
include HmacConcern
|
||||||
before_action :ensure_inbox, only: [:create]
|
before_action :ensure_inbox, only: [:create]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@contact_inbox = ContactInboxBuilder.new(
|
@contact_inbox = ContactInboxBuilder.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
inbox: @inbox,
|
inbox: @inbox,
|
||||||
source_id: params[:source_id]
|
source_id: params[:source_id],
|
||||||
|
hmac_verified: hmac_verified?
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
|||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def export
|
||||||
|
column_names = params['column_names']
|
||||||
|
Account::ContactsExportJob.perform_later(Current.account.id, column_names)
|
||||||
|
head :ok, message: I18n.t('errors.contacts.export.success')
|
||||||
|
end
|
||||||
|
|
||||||
# returns online contacts
|
# returns online contacts
|
||||||
def active
|
def active
|
||||||
contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
|
contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
|
||||||
include EnsureCurrentAccountHelper
|
|
||||||
before_action :conversation
|
before_action :conversation
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
||||||
include Events::Types
|
include Events::Types
|
||||||
include DateRangeHelper
|
include DateRangeHelper
|
||||||
|
include HmacConcern
|
||||||
|
|
||||||
before_action :conversation, except: [:index, :meta, :search, :create, :filter]
|
before_action :conversation, except: [:index, :meta, :search, :create, :filter]
|
||||||
before_action :inbox, :contact, :contact_inbox, only: [:create]
|
before_action :inbox, :contact, :contact_inbox, only: [:create]
|
||||||
@@ -104,9 +105,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_conversation_status
|
def set_conversation_status
|
||||||
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
|
|
||||||
# commenting this out to see if there are any errors, if not we can remove this in subsequent releases
|
|
||||||
# status = params[:status] == 'bot' ? 'pending' : params[:status]
|
|
||||||
@conversation.status = params[:status]
|
@conversation.status = params[:status]
|
||||||
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
|
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
|
||||||
end
|
end
|
||||||
@@ -152,7 +150,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
ContactInboxBuilder.new(
|
ContactInboxBuilder.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
inbox: @inbox,
|
inbox: @inbox,
|
||||||
source_id: params[:source_id]
|
source_id: params[:source_id],
|
||||||
|
hmac_verified: hmac_verified?
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :check_authorization
|
||||||
before_action :fetch_custom_filters, except: [:create]
|
before_action :fetch_custom_filters, except: [:create]
|
||||||
before_action :fetch_custom_filter, only: [:show, :update, :destroy]
|
before_action :fetch_custom_filter, only: [:show, :update, :destroy]
|
||||||
DEFAULT_FILTER_TYPE = 'conversation'.freeze
|
DEFAULT_FILTER_TYPE = 'conversation'.freeze
|
||||||
@@ -11,6 +12,7 @@ class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseContro
|
|||||||
@custom_filter = current_user.custom_filters.create!(
|
@custom_filter = current_user.custom_filters.create!(
|
||||||
permitted_payload.merge(account_id: Current.account.id)
|
permitted_payload.merge(account_id: Current.account.id)
|
||||||
)
|
)
|
||||||
|
render json: { error: @custom_filter.errors.messages }, status: :unprocessable_entity and return unless @custom_filter.valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|||||||
@@ -44,26 +44,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
def update
|
def update
|
||||||
@inbox.update!(permitted_params.except(:channel))
|
@inbox.update!(permitted_params.except(:channel))
|
||||||
update_inbox_working_hours
|
update_inbox_working_hours
|
||||||
channel_attributes = get_channel_attributes(@inbox.channel_type)
|
update_channel if channel_update_required?
|
||||||
|
|
||||||
# Inbox update doesn't necessarily need channel attributes
|
|
||||||
return if permitted_params(channel_attributes)[:channel].blank?
|
|
||||||
|
|
||||||
if @inbox.inbox_type == 'Email'
|
|
||||||
begin
|
|
||||||
validate_email_channel(channel_attributes)
|
|
||||||
rescue StandardError => e
|
|
||||||
render json: { message: e }, status: :unprocessable_entity and return
|
|
||||||
end
|
|
||||||
@inbox.channel.reauthorized!
|
|
||||||
end
|
|
||||||
|
|
||||||
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
|
|
||||||
update_channel_feature_flags
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_inbox_working_hours
|
|
||||||
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def agent_bot
|
def agent_bot
|
||||||
@@ -82,7 +63,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
::DeleteObjectJob.perform_later(@inbox) if @inbox.present?
|
::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present?
|
||||||
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
|
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -103,6 +84,35 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
|
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_inbox_working_hours
|
||||||
|
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_channel
|
||||||
|
channel_attributes = get_channel_attributes(@inbox.channel_type)
|
||||||
|
return if permitted_params(channel_attributes)[:channel].blank?
|
||||||
|
|
||||||
|
validate_and_update_email_channel(channel_attributes) if @inbox.inbox_type == 'Email'
|
||||||
|
|
||||||
|
reauthorize_and_update_channel(channel_attributes)
|
||||||
|
update_channel_feature_flags
|
||||||
|
end
|
||||||
|
|
||||||
|
def channel_update_required?
|
||||||
|
permitted_params(get_channel_attributes(@inbox.channel_type))[:channel].present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_and_update_email_channel(channel_attributes)
|
||||||
|
validate_email_channel(channel_attributes)
|
||||||
|
rescue StandardError => e
|
||||||
|
render json: { message: e }, status: :unprocessable_entity and return
|
||||||
|
end
|
||||||
|
|
||||||
|
def reauthorize_and_update_channel(channel_attributes)
|
||||||
|
@inbox.channel.reauthorized! if @inbox.channel.respond_to?(:reauthorized!)
|
||||||
|
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
|
||||||
|
end
|
||||||
|
|
||||||
def update_channel_feature_flags
|
def update_channel_feature_flags
|
||||||
return unless @inbox.web_widget?
|
return unless @inbox.web_widget?
|
||||||
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
|
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
|
||||||
@@ -114,7 +124,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
def inbox_attributes
|
def inbox_attributes
|
||||||
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
|
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
|
||||||
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
|
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
|
||||||
:lock_to_single_conversation, :portal_id]
|
:lock_to_single_conversation, :portal_id, :sender_name_type, :business_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted_params(channel_attributes = [])
|
def permitted_params(channel_attributes = [])
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cache_keys
|
def cache_keys
|
||||||
|
expires_in 10.seconds, public: false, stale_while_revalidate: 5.minutes
|
||||||
render json: { cache_keys: get_cache_keys }, status: :ok
|
render json: { cache_keys: get_cache_keys }, status: :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
|||||||
@message.update!(submitted_email: contact_email)
|
@message.update!(submitted_email: contact_email)
|
||||||
ContactIdentifyAction.new(
|
ContactIdentifyAction.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
params: { email: contact_email, name: contact_name }
|
params: { email: contact_email, name: contact_name },
|
||||||
|
retain_original_contact_name: true
|
||||||
).perform
|
).perform
|
||||||
else
|
else
|
||||||
@message.update!(message_update_params[:message])
|
@message.update!(message_update_params[:message])
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module HmacConcern
|
||||||
|
def hmac_verified?
|
||||||
|
ActiveModel::Type::Boolean.new.cast(params[:hmac_verified]).present?
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
class DashboardController < ActionController::Base
|
class DashboardController < ActionController::Base
|
||||||
include SwitchLocale
|
include SwitchLocale
|
||||||
|
|
||||||
|
before_action :set_application_pack
|
||||||
before_action :set_global_config
|
before_action :set_global_config
|
||||||
around_action :switch_locale
|
around_action :switch_locale
|
||||||
before_action :ensure_installation_onboarding, only: [:index]
|
before_action :ensure_installation_onboarding, only: [:index]
|
||||||
@@ -14,7 +15,7 @@ class DashboardController < ActionController::Base
|
|||||||
|
|
||||||
def set_global_config
|
def set_global_config
|
||||||
@global_config = GlobalConfig.get(
|
@global_config = GlobalConfig.get(
|
||||||
'LOGO', 'LOGO_THUMBNAIL',
|
'LOGO', 'LOGO_DARK', 'LOGO_THUMBNAIL',
|
||||||
'INSTALLATION_NAME',
|
'INSTALLATION_NAME',
|
||||||
'WIDGET_BRAND_URL', 'TERMS_URL',
|
'WIDGET_BRAND_URL', 'TERMS_URL',
|
||||||
'PRIVACY_URL',
|
'PRIVACY_URL',
|
||||||
@@ -60,4 +61,12 @@ class DashboardController < ActionController::Base
|
|||||||
GIT_SHA: GIT_HASH
|
GIT_SHA: GIT_HASH
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_application_pack
|
||||||
|
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
|
||||||
|
'v3app'
|
||||||
|
else
|
||||||
|
'application'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -37,3 +37,5 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
|
|||||||
@resource = user if user&.valid_sso_auth_token?(params[:sso_auth_token])
|
@resource = user if user&.valid_sso_auth_token?(params[:sso_auth_token])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
DeviseOverrides::SessionsController.prepend_mod_with('DeviseOverrides::SessionsController')
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_article_content(content)
|
def render_article_content(content)
|
||||||
# rubocop:disable Rails/OutputSafety
|
ChatwootMarkdownRenderer.new(content).render_article
|
||||||
CommonMarker.render_html(content).html_safe
|
|
||||||
# rubocop:enable Rails/OutputSafety
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
|||||||
def set_locale(&)
|
def set_locale(&)
|
||||||
switch_locale_with_portal(&) if params[:locale].present?
|
switch_locale_with_portal(&) if params[:locale].present?
|
||||||
switch_locale_with_article(&) if params[:article_slug].present?
|
switch_locale_with_article(&) if params[:article_slug].present?
|
||||||
|
|
||||||
|
yield
|
||||||
end
|
end
|
||||||
|
|
||||||
def switch_locale_with_portal(&)
|
def switch_locale_with_portal(&)
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
|
|||||||
# rubocop:enable Rails/I18nLocaleTexts
|
# rubocop:enable Rails/I18nLocaleTexts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_cache
|
||||||
|
requested_resource.reset_cache_keys
|
||||||
|
# rubocop:disable Rails/I18nLocaleTexts
|
||||||
|
redirect_back(fallback_location: [namespace, requested_resource], notice: 'Cache keys cleared')
|
||||||
|
# rubocop:enable Rails/I18nLocaleTexts
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
account = Account.find(params[:id])
|
account = Account.find(params[:id])
|
||||||
|
|
||||||
|
|||||||
@@ -41,4 +41,14 @@ class SuperAdmin::AgentBotsController < SuperAdmin::ApplicationController
|
|||||||
|
|
||||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
# for more information
|
# for more information
|
||||||
|
|
||||||
|
def destroy_avatar
|
||||||
|
avatar = requested_resource.avatar
|
||||||
|
avatar.purge
|
||||||
|
redirect_back(fallback_location: super_admin_agent_bots_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def scoped_resource
|
||||||
|
resource_class.with_attached_avatar
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
|
|||||||
sha
|
sha
|
||||||
postgres_status
|
postgres_status
|
||||||
redis_metrics
|
redis_metrics
|
||||||
|
chatwoot_edition
|
||||||
|
end
|
||||||
|
|
||||||
|
def chatwoot_edition
|
||||||
|
@metrics['Chatwoot edition'] = if ChatwootApp.enterprise?
|
||||||
|
'Enterprise'
|
||||||
|
elsif ChatwootApp.custom?
|
||||||
|
'Custom'
|
||||||
|
else
|
||||||
|
'Community'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def chatwoot_version
|
def chatwoot_version
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ class SuperAdmin::UsersController < SuperAdmin::ApplicationController
|
|||||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
# and `dashboard`:
|
# and `dashboard`:
|
||||||
#
|
#
|
||||||
|
|
||||||
|
def destroy_avatar
|
||||||
|
avatar = requested_resource.avatar
|
||||||
|
avatar.purge
|
||||||
|
redirect_back(fallback_location: super_admin_users_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def scoped_resource
|
||||||
|
resource_class.with_attached_avatar
|
||||||
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
permitted_params = super
|
permitted_params = super
|
||||||
permitted_params.delete(:password) if permitted_params[:password].blank?
|
permitted_params.delete(:password) if permitted_params[:password].blank?
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ class AgentBotDashboard < Administrate::BaseDashboard
|
|||||||
ATTRIBUTE_TYPES = {
|
ATTRIBUTE_TYPES = {
|
||||||
access_token: Field::HasOne,
|
access_token: Field::HasOne,
|
||||||
avatar_url: AvatarField,
|
avatar_url: AvatarField,
|
||||||
|
avatar: Field::ActiveStorage.with_options(
|
||||||
|
destroy_url: proc do |_namespace, _resource, attachment|
|
||||||
|
[:avatar_super_admin_agent_bot, { attachment_id: attachment.id }]
|
||||||
|
end
|
||||||
|
),
|
||||||
id: Field::Number,
|
id: Field::Number,
|
||||||
name: Field::String,
|
name: Field::String,
|
||||||
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
||||||
@@ -36,6 +41,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
|
|||||||
# an array of attributes that will be displayed on the model's show page.
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
SHOW_PAGE_ATTRIBUTES = %i[
|
SHOW_PAGE_ATTRIBUTES = %i[
|
||||||
id
|
id
|
||||||
|
avatar_url
|
||||||
account
|
account
|
||||||
name
|
name
|
||||||
description
|
description
|
||||||
@@ -47,6 +53,7 @@ class AgentBotDashboard < Administrate::BaseDashboard
|
|||||||
# on the model's form (`new` and `edit`) pages.
|
# on the model's form (`new` and `edit`) pages.
|
||||||
FORM_ATTRIBUTES = %i[
|
FORM_ATTRIBUTES = %i[
|
||||||
name
|
name
|
||||||
|
avatar
|
||||||
account
|
account
|
||||||
description
|
description
|
||||||
outgoing_url
|
outgoing_url
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ class UserDashboard < Administrate::BaseDashboard
|
|||||||
account_users: Field::HasMany,
|
account_users: Field::HasMany,
|
||||||
id: Field::Number,
|
id: Field::Number,
|
||||||
avatar_url: AvatarField,
|
avatar_url: AvatarField,
|
||||||
|
avatar: Field::ActiveStorage.with_options(
|
||||||
|
destroy_url: proc do |_namespace, _resource, attachment|
|
||||||
|
[:avatar_super_admin_user, { attachment_id: attachment.id }]
|
||||||
|
end
|
||||||
|
),
|
||||||
provider: Field::String,
|
provider: Field::String,
|
||||||
uid: Field::String,
|
uid: Field::String,
|
||||||
password: Field::Password,
|
password: Field::Password,
|
||||||
@@ -69,6 +74,7 @@ class UserDashboard < Administrate::BaseDashboard
|
|||||||
# on the model's form (`new` and `edit`) pages.
|
# on the model's form (`new` and `edit`) pages.
|
||||||
FORM_ATTRIBUTES = %i[
|
FORM_ATTRIBUTES = %i[
|
||||||
name
|
name
|
||||||
|
avatar
|
||||||
display_name
|
display_name
|
||||||
email
|
email
|
||||||
password
|
password
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ class ConversationFinder
|
|||||||
latest: 'latest',
|
latest: 'latest',
|
||||||
sort_on_created_at: 'sort_on_created_at',
|
sort_on_created_at: 'sort_on_created_at',
|
||||||
last_user_message_at: 'last_user_message_at',
|
last_user_message_at: 'last_user_message_at',
|
||||||
sort_on_priority: 'sort_on_priority'
|
sort_on_priority: 'sort_on_priority',
|
||||||
|
sort_on_waiting_since: 'sort_on_waiting_since'
|
||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
|
|
||||||
# assumptions
|
# assumptions
|
||||||
@@ -102,7 +103,7 @@ class ConversationFinder
|
|||||||
when 'participating'
|
when 'participating'
|
||||||
@conversations = current_user.participating_conversations.where(account_id: current_account.id)
|
@conversations = current_user.participating_conversations.where(account_id: current_account.id)
|
||||||
when 'unattended'
|
when 'unattended'
|
||||||
@conversations = @conversations.where(first_reply_created_at: nil)
|
@conversations = @conversations.unattended
|
||||||
end
|
end
|
||||||
@conversations
|
@conversations
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
module BillingHelper
|
||||||
|
private
|
||||||
|
|
||||||
|
def default_plan?(account)
|
||||||
|
installation_config = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS')
|
||||||
|
default_plan = installation_config&.value&.first
|
||||||
|
|
||||||
|
# Return false if not plans are configured, so that no checks are enforced
|
||||||
|
return false if default_plan.blank?
|
||||||
|
|
||||||
|
account.custom_attributes['plan_name'].nil? || account.custom_attributes['plan_name'] == default_plan['name']
|
||||||
|
end
|
||||||
|
|
||||||
|
def conversations_this_month(account)
|
||||||
|
account.conversations.where('created_at > ?', 30.days.ago).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def non_web_inboxes(account)
|
||||||
|
account.inboxes.where.not(channel_type: Channel::WebWidget.to_s).count
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,8 +7,6 @@ module MessageFormatHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_message_content(message_content)
|
def render_message_content(message_content)
|
||||||
# rubocop:disable Rails/OutputSafety
|
ChatwootMarkdownRenderer.new(message_content).render_message
|
||||||
CommonMarker.render_html(message_content).html_safe
|
|
||||||
# rubocop:enable Rails/OutputSafety
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ module ReportHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def resolutions_count
|
def resolutions_count
|
||||||
(get_grouped_values scope.conversations.where(account_id: account.id).resolved).count
|
object_scope = scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved,
|
||||||
|
conversations: { status: :resolved }).distinct
|
||||||
|
(get_grouped_values object_scope).count
|
||||||
end
|
end
|
||||||
|
|
||||||
def avg_first_response_time
|
def avg_first_response_time
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||||
>
|
>
|
||||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||||
|
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
|
||||||
|
<payment-pending-banner />
|
||||||
|
<upgrade-banner />
|
||||||
|
</template>
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<router-view />
|
<router-view />
|
||||||
</transition>
|
</transition>
|
||||||
@@ -25,6 +29,8 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
|
|||||||
import LoadingState from './components/widgets/LoadingState.vue';
|
import LoadingState from './components/widgets/LoadingState.vue';
|
||||||
import NetworkNotification from './components/NetworkNotification';
|
import NetworkNotification from './components/NetworkNotification';
|
||||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||||
|
import UpgradeBanner from './components/app/UpgradeBanner.vue';
|
||||||
|
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
||||||
import vueActionCable from './helper/actionCable';
|
import vueActionCable from './helper/actionCable';
|
||||||
import WootSnackbarBox from './components/SnackbarContainer';
|
import WootSnackbarBox from './components/SnackbarContainer';
|
||||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||||
@@ -41,7 +47,9 @@ export default {
|
|||||||
LoadingState,
|
LoadingState,
|
||||||
NetworkNotification,
|
NetworkNotification,
|
||||||
UpdateBanner,
|
UpdateBanner,
|
||||||
|
PaymentPendingBanner,
|
||||||
WootSnackbarBox,
|
WootSnackbarBox,
|
||||||
|
UpgradeBanner,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [rtlMixin],
|
mixins: [rtlMixin],
|
||||||
@@ -59,6 +67,7 @@ export default {
|
|||||||
currentUser: 'getCurrentUser',
|
currentUser: 'getCurrentUser',
|
||||||
globalConfig: 'globalConfig/get',
|
globalConfig: 'globalConfig/get',
|
||||||
authUIFlags: 'getAuthUIFlags',
|
authUIFlags: 'getAuthUIFlags',
|
||||||
|
accountUIFlags: 'accounts/getUIFlags',
|
||||||
currentAccountId: 'getCurrentAccountId',
|
currentAccountId: 'getCurrentAccountId',
|
||||||
}),
|
}),
|
||||||
hasAccounts() {
|
hasAccounts() {
|
||||||
@@ -115,11 +124,6 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import './assets/scss/app';
|
@import './assets/scss/app';
|
||||||
.update-banner {
|
|
||||||
height: var(--space-larger);
|
|
||||||
align-items: center;
|
|
||||||
font-size: var(--font-size-small) !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
||||||
|
|||||||
@@ -3,47 +3,11 @@
|
|||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import endPoints from './endPoints';
|
import endPoints from './endPoints';
|
||||||
import {
|
import {
|
||||||
setAuthCredentials,
|
|
||||||
clearCookiesOnLogout,
|
clearCookiesOnLogout,
|
||||||
deleteIndexedDBOnLogout,
|
deleteIndexedDBOnLogout,
|
||||||
} from '../store/utils/api';
|
} from '../store/utils/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
login(creds) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.post('auth/sign_in', creds)
|
|
||||||
.then(response => {
|
|
||||||
setAuthCredentials(response);
|
|
||||||
resolve(response.data);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error.response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
register(creds) {
|
|
||||||
const urlData = endPoints('register');
|
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.post(urlData.url, {
|
|
||||||
account_name: creds.accountName.trim(),
|
|
||||||
user_full_name: creds.fullName.trim(),
|
|
||||||
email: creds.email,
|
|
||||||
password: creds.password,
|
|
||||||
h_captcha_client_response: creds.hCaptchaClientResponse,
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
setAuthCredentials(response);
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return fetchPromise;
|
|
||||||
},
|
|
||||||
validityCheck() {
|
validityCheck() {
|
||||||
const urlData = endPoints('validityCheck');
|
const urlData = endPoints('validityCheck');
|
||||||
return axios.get(urlData.url);
|
return axios.get(urlData.url);
|
||||||
@@ -73,45 +37,6 @@ export default {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
verifyPasswordToken({ confirmationToken }) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.post('auth/confirmation', {
|
|
||||||
confirmation_token: confirmationToken,
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
setAuthCredentials(response);
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error.response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setNewPassword({ resetPasswordToken, password, confirmPassword }) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.put('auth/password', {
|
|
||||||
reset_password_token: resetPasswordToken,
|
|
||||||
password_confirmation: confirmPassword,
|
|
||||||
password,
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
setAuthCredentials(response);
|
|
||||||
resolve(response);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error.response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetPassword({ email }) {
|
|
||||||
const urlData = endPoints('resetPassword');
|
|
||||||
return axios.post(urlData.url, { email });
|
|
||||||
},
|
|
||||||
|
|
||||||
profileUpdate({
|
profileUpdate({
|
||||||
password,
|
password,
|
||||||
password_confirmation,
|
password_confirmation,
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ class ContactAPI extends ApiClient {
|
|||||||
destroyAvatar(contactId) {
|
destroyAvatar(contactId) {
|
||||||
return axios.delete(`${this.url}/${contactId}/avatar`);
|
return axios.delete(`${this.url}/${contactId}/avatar`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportContacts() {
|
||||||
|
return axios.get(`${this.url}/export`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ContactAPI();
|
export default new ContactAPI();
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ class CSATReportsAPI extends ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetrics({ from, to, user_ids, inbox_id, team_id } = {}) {
|
getMetrics({ from, to, user_ids, inbox_id, team_id, rating } = {}) {
|
||||||
// no ratings for metrics
|
// no ratings for metrics
|
||||||
return axios.get(`${this.url}/metrics`, {
|
return axios.get(`${this.url}/metrics`, {
|
||||||
params: { since: from, until: to, user_ids, inbox_id, team_id },
|
params: { since: from, until: to, user_ids, inbox_id, team_id, rating },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ class EnterpriseAccountAPI extends ApiClient {
|
|||||||
subscription() {
|
subscription() {
|
||||||
return axios.post(`${this.url}subscription`);
|
return axios.post(`${this.url}subscription`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLimits() {
|
||||||
|
return axios.get(`${this.url}limits`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new EnterpriseAccountAPI();
|
export default new EnterpriseAccountAPI();
|
||||||
|
|||||||
@@ -127,6 +127,10 @@ class ConversationApi extends ApiClient {
|
|||||||
user_ids: userIds,
|
user_ids: userIds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllAttachments(conversationId) {
|
||||||
|
return axios.get(`${this.url}/${conversationId}/attachments`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ConversationApi();
|
export default new ConversationApi();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const buildCreatePayload = ({
|
|||||||
files,
|
files,
|
||||||
ccEmails = '',
|
ccEmails = '',
|
||||||
bccEmails = '',
|
bccEmails = '',
|
||||||
|
toEmails = '',
|
||||||
templateParams,
|
templateParams,
|
||||||
}) => {
|
}) => {
|
||||||
let payload;
|
let payload;
|
||||||
@@ -25,6 +26,9 @@ export const buildCreatePayload = ({
|
|||||||
payload.append('echo_id', echoId);
|
payload.append('echo_id', echoId);
|
||||||
payload.append('cc_emails', ccEmails);
|
payload.append('cc_emails', ccEmails);
|
||||||
payload.append('bcc_emails', bccEmails);
|
payload.append('bcc_emails', bccEmails);
|
||||||
|
if (toEmails) {
|
||||||
|
payload.append('to_emails', toEmails);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
payload = {
|
payload = {
|
||||||
content: message,
|
content: message,
|
||||||
@@ -33,6 +37,7 @@ export const buildCreatePayload = ({
|
|||||||
content_attributes: contentAttributes,
|
content_attributes: contentAttributes,
|
||||||
cc_emails: ccEmails,
|
cc_emails: ccEmails,
|
||||||
bcc_emails: bccEmails,
|
bcc_emails: bccEmails,
|
||||||
|
to_emails: toEmails,
|
||||||
template_params: templateParams,
|
template_params: templateParams,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -53,6 +58,7 @@ class MessageApi extends ApiClient {
|
|||||||
files,
|
files,
|
||||||
ccEmails = '',
|
ccEmails = '',
|
||||||
bccEmails = '',
|
bccEmails = '',
|
||||||
|
toEmails = '',
|
||||||
templateParams,
|
templateParams,
|
||||||
}) {
|
}) {
|
||||||
return axios({
|
return axios({
|
||||||
@@ -66,6 +72,7 @@ class MessageApi extends ApiClient {
|
|||||||
files,
|
files,
|
||||||
ccEmails,
|
ccEmails,
|
||||||
bccEmails,
|
bccEmails,
|
||||||
|
toEmails,
|
||||||
templateParams,
|
templateParams,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -210,5 +210,12 @@ describe('#ConversationAPI', () => {
|
|||||||
{ params: { page: payload.page } }
|
{ params: { page: payload.page } }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#getAllAttachments', () => {
|
||||||
|
conversationAPI.getAllAttachments(1);
|
||||||
|
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/conversations/1/attachments'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
border-radius: var(--border-radius-normal);
|
border-radius: var(--border-radius-normal);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 4.0rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx-input:disabled,
|
.mx-input:disabled,
|
||||||
@@ -35,3 +35,32 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx-datepicker-inline {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mx-calendar {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.disabled {
|
||||||
|
background-color: var(--s-25);
|
||||||
|
color: var(--s-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-time-item.disabled {
|
||||||
|
background-color: var(--s-25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.today {
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-datepicker-main {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-time-header {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
height: 4.0rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
// Disable contrast warnings in Foundation.
|
// Disable contrast warnings in Foundation.
|
||||||
$contrast-warnings: false;
|
$contrast-warnings: false;
|
||||||
|
|
||||||
$global-font-size: 10px;
|
$global-font-size: 16px;
|
||||||
$global-width: 100%;
|
$global-width: 100%;
|
||||||
$global-lineheight: 1.5;
|
$global-lineheight: 1.5;
|
||||||
$foundation-palette: (primary: $color-woot,
|
$foundation-palette: (primary: $color-woot,
|
||||||
@@ -63,7 +63,7 @@ $black: #000;
|
|||||||
$white: #fff;
|
$white: #fff;
|
||||||
$body-background: $white;
|
$body-background: $white;
|
||||||
$body-font-color: $color-body;
|
$body-font-color: $color-body;
|
||||||
$body-font-family: 'Inter',
|
$body-font-family: 'PlusJakarta',
|
||||||
-apple-system,
|
-apple-system,
|
||||||
system-ui,
|
system-ui,
|
||||||
BlinkMacSystemFont,
|
BlinkMacSystemFont,
|
||||||
@@ -117,7 +117,7 @@ $header-font-style: normal;
|
|||||||
$font-family-monospace: $body-font-family;
|
$font-family-monospace: $body-font-family;
|
||||||
$header-color: $color-heading;
|
$header-color: $color-heading;
|
||||||
$header-lineheight: 1.4;
|
$header-lineheight: 1.4;
|
||||||
$header-margin-bottom: 0.5rem;
|
$header-margin-bottom: 0.3125rem;
|
||||||
$header-styles: (small: ("h1": ("font-size": 24),
|
$header-styles: (small: ("h1": ("font-size": 24),
|
||||||
"h2": ("font-size": 20),
|
"h2": ("font-size": 20),
|
||||||
"h3": ("font-size": 19),
|
"h3": ("font-size": 19),
|
||||||
@@ -133,7 +133,7 @@ $header-styles: (small: ("h1": ("font-size": 24),
|
|||||||
$header-text-rendering: optimizeLegibility;
|
$header-text-rendering: optimizeLegibility;
|
||||||
$small-font-size: 80%;
|
$small-font-size: 80%;
|
||||||
$header-small-font-color: $medium-gray;
|
$header-small-font-color: $medium-gray;
|
||||||
$paragraph-lineheight: 1.45;
|
$paragraph-lineheight: 1.65;
|
||||||
$paragraph-margin-bottom: var(--space-small);
|
$paragraph-margin-bottom: var(--space-small);
|
||||||
$paragraph-text-rendering: optimizeLegibility;
|
$paragraph-text-rendering: optimizeLegibility;
|
||||||
$code-color: $black;
|
$code-color: $black;
|
||||||
@@ -153,11 +153,11 @@ $list-lineheight: $paragraph-lineheight;
|
|||||||
$list-margin-bottom: $paragraph-margin-bottom;
|
$list-margin-bottom: $paragraph-margin-bottom;
|
||||||
$list-style-type: disc;
|
$list-style-type: disc;
|
||||||
$list-style-position: outside;
|
$list-style-position: outside;
|
||||||
$list-side-margin: 1.25rem;
|
$list-side-margin: 0.78125rem;
|
||||||
$list-nested-side-margin: 1.25rem;
|
$list-nested-side-margin: 0.78125rem;
|
||||||
$defnlist-margin-bottom: 1rem;
|
$defnlist-margin-bottom: 0.6875rem;
|
||||||
$defnlist-term-weight: $global-weight-bold;
|
$defnlist-term-weight: $global-weight-bold;
|
||||||
$defnlist-term-margin-bottom: 0.3rem;
|
$defnlist-term-margin-bottom: 0.1875rem;
|
||||||
$blockquote-color: $dark-gray;
|
$blockquote-color: $dark-gray;
|
||||||
$blockquote-padding: rem-calc(9 20 0 19);
|
$blockquote-padding: rem-calc(9 20 0 19);
|
||||||
$blockquote-border: 1px solid $medium-gray;
|
$blockquote-border: 1px solid $medium-gray;
|
||||||
@@ -179,9 +179,9 @@ $lead-lineheight: 1.6;
|
|||||||
$subheader-lineheight: 1.4;
|
$subheader-lineheight: 1.4;
|
||||||
$subheader-color: $dark-gray;
|
$subheader-color: $dark-gray;
|
||||||
$subheader-font-weight: $global-weight-normal;
|
$subheader-font-weight: $global-weight-normal;
|
||||||
$subheader-margin-top: 0.2rem;
|
$subheader-margin-top: 0.125rem;
|
||||||
$subheader-margin-bottom: 0.5rem;
|
$subheader-margin-bottom: 0.3125rem;
|
||||||
$stat-font-size: 2.5rem;
|
$stat-font-size: 1.5625rem;
|
||||||
|
|
||||||
// 6. Abide
|
// 6. Abide
|
||||||
// --------
|
// --------
|
||||||
@@ -202,11 +202,11 @@ $accordion-plusminus: true;
|
|||||||
$accordion-title-font-size: rem-calc(12);
|
$accordion-title-font-size: rem-calc(12);
|
||||||
$accordion-item-color: $primary-color;
|
$accordion-item-color: $primary-color;
|
||||||
$accordion-item-background-hover: $light-gray;
|
$accordion-item-background-hover: $light-gray;
|
||||||
$accordion-item-padding: 1.25rem 1rem;
|
$accordion-item-padding: 0.78125rem 0.625rem;
|
||||||
$accordion-content-background: $white;
|
$accordion-content-background: $white;
|
||||||
$accordion-content-border: 1px solid $light-gray;
|
$accordion-content-border: 1px solid $light-gray;
|
||||||
$accordion-content-color: $body-font-color;
|
$accordion-content-color: $body-font-color;
|
||||||
$accordion-content-padding: 1rem;
|
$accordion-content-padding: 0.625rem;
|
||||||
|
|
||||||
// 8. Accordion Menu
|
// 8. Accordion Menu
|
||||||
// -----------------
|
// -----------------
|
||||||
@@ -234,7 +234,7 @@ $breadcrumbs-item-font-size: rem-calc(11);
|
|||||||
$breadcrumbs-item-color: $primary-color;
|
$breadcrumbs-item-color: $primary-color;
|
||||||
$breadcrumbs-item-color-current: $black;
|
$breadcrumbs-item-color-current: $black;
|
||||||
$breadcrumbs-item-color-disabled: $medium-gray;
|
$breadcrumbs-item-color-disabled: $medium-gray;
|
||||||
$breadcrumbs-item-margin: 0.75rem;
|
$breadcrumbs-item-margin: 0.46875rem;
|
||||||
$breadcrumbs-item-uppercase: true;
|
$breadcrumbs-item-uppercase: true;
|
||||||
$breadcrumbs-item-slash: true;
|
$breadcrumbs-item-slash: true;
|
||||||
|
|
||||||
@@ -275,8 +275,8 @@ $buttongroup-radius-on-each: false;
|
|||||||
$callout-background: $white;
|
$callout-background: $white;
|
||||||
$callout-background-fade: 85%;
|
$callout-background-fade: 85%;
|
||||||
$callout-border: 1px solid rgba($black, 0.25);
|
$callout-border: 1px solid rgba($black, 0.25);
|
||||||
$callout-margin: 0 0 1rem 0;
|
$callout-margin: 0 0 0.625rem 0;
|
||||||
$callout-padding: 1rem;
|
$callout-padding: 0.625rem;
|
||||||
$callout-font-color: $body-font-color;
|
$callout-font-color: $body-font-color;
|
||||||
$callout-font-color-alt: $body-background;
|
$callout-font-color-alt: $body-background;
|
||||||
$callout-radius: $global-radius;
|
$callout-radius: $global-radius;
|
||||||
@@ -320,10 +320,10 @@ $drilldown-background: $white;
|
|||||||
// 17. Dropdown
|
// 17. Dropdown
|
||||||
// ------------
|
// ------------
|
||||||
|
|
||||||
$dropdown-padding: 1rem;
|
$dropdown-padding: 0.625rem;
|
||||||
$dropdown-background: $body-background;
|
$dropdown-background: $body-background;
|
||||||
$dropdown-border: 1px solid $medium-gray;
|
$dropdown-border: 1px solid $medium-gray;
|
||||||
$dropdown-font-size: 1rem;
|
$dropdown-font-size: 0.625rem;
|
||||||
$dropdown-width: 300px;
|
$dropdown-width: 300px;
|
||||||
$dropdown-radius: $global-radius;
|
$dropdown-radius: $global-radius;
|
||||||
$dropdown-sizes: (tiny: 100px,
|
$dropdown-sizes: (tiny: 100px,
|
||||||
@@ -354,7 +354,7 @@ $helptext-font-style: italic;
|
|||||||
$input-prefix-color: $color-body;
|
$input-prefix-color: $color-body;
|
||||||
$input-prefix-background: var(--b-100);
|
$input-prefix-background: var(--b-100);
|
||||||
$input-prefix-border: 1px solid $color-border;
|
$input-prefix-border: 1px solid $color-border;
|
||||||
$input-prefix-padding: 1rem;
|
$input-prefix-padding: 0.625rem;
|
||||||
$form-label-color: $color-body;
|
$form-label-color: $color-body;
|
||||||
$form-label-font-size: rem-calc(14);
|
$form-label-font-size: rem-calc(14);
|
||||||
$form-label-font-weight: $font-weight-medium;
|
$form-label-font-weight: $font-weight-medium;
|
||||||
@@ -406,14 +406,14 @@ $menu-margin-nested: $space-medium;
|
|||||||
$menu-item-padding: $space-slab;
|
$menu-item-padding: $space-slab;
|
||||||
$menu-item-color-active: $white;
|
$menu-item-color-active: $white;
|
||||||
$menu-item-background-active: $color-background;
|
$menu-item-background-active: $color-background;
|
||||||
$menu-icon-spacing: 0.25rem;
|
$menu-icon-spacing: 0.15625rem;
|
||||||
$menu-item-background-hover: $light-gray;
|
$menu-item-background-hover: $light-gray;
|
||||||
$menu-border: $light-gray;
|
$menu-border: $light-gray;
|
||||||
|
|
||||||
// 23. Meter
|
// 23. Meter
|
||||||
// ---------
|
// ---------
|
||||||
|
|
||||||
$meter-height: 1rem;
|
$meter-height: 0.625rem;
|
||||||
$meter-radius: $global-radius;
|
$meter-radius: $global-radius;
|
||||||
$meter-background: $medium-gray;
|
$meter-background: $medium-gray;
|
||||||
$meter-fill-good: $success-color;
|
$meter-fill-good: $success-color;
|
||||||
@@ -423,11 +423,11 @@ $meter-fill-bad: $alert-color;
|
|||||||
// 24. Off-canvas
|
// 24. Off-canvas
|
||||||
// --------------
|
// --------------
|
||||||
|
|
||||||
$offcanvas-sizes: (small: 23rem,
|
$offcanvas-sizes: (small: 14.375,
|
||||||
medium: 23rem,
|
medium: 14.375,
|
||||||
);
|
);
|
||||||
$offcanvas-vertical-sizes: (small: 23rem,
|
$offcanvas-vertical-sizes: (small: 14.375,
|
||||||
medium: 23rem,
|
medium: 14.375,
|
||||||
);
|
);
|
||||||
$offcanvas-background: $light-gray;
|
$offcanvas-background: $light-gray;
|
||||||
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
|
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
|
||||||
@@ -445,14 +445,14 @@ $maincontent-class: 'off-canvas-content';
|
|||||||
|
|
||||||
$orbit-bullet-background: $medium-gray;
|
$orbit-bullet-background: $medium-gray;
|
||||||
$orbit-bullet-background-active: $dark-gray;
|
$orbit-bullet-background-active: $dark-gray;
|
||||||
$orbit-bullet-diameter: 1.2rem;
|
$orbit-bullet-diameter: 0.75rem;
|
||||||
$orbit-bullet-margin: 0.1rem;
|
$orbit-bullet-margin: 0.0625rem;
|
||||||
$orbit-bullet-margin-top: 0.8rem;
|
$orbit-bullet-margin-top: 0.5rem;
|
||||||
$orbit-bullet-margin-bottom: 0.8rem;
|
$orbit-bullet-margin-bottom: 0.5rem;
|
||||||
$orbit-caption-background: rgba($black, 0.5);
|
$orbit-caption-background: rgba($black, 0.5);
|
||||||
$orbit-caption-padding: 1rem;
|
$orbit-caption-padding: 0.625rem;
|
||||||
$orbit-control-background-hover: rgba($black, 0.5);
|
$orbit-control-background-hover: rgba($black, 0.5);
|
||||||
$orbit-control-padding: 1rem;
|
$orbit-control-padding: 0.625rem;
|
||||||
$orbit-control-zindex: 10;
|
$orbit-control-zindex: 10;
|
||||||
|
|
||||||
// 26. Pagination
|
// 26. Pagination
|
||||||
@@ -476,7 +476,7 @@ $pagination-arrows: true;
|
|||||||
// 27. Progress Bar
|
// 27. Progress Bar
|
||||||
// ----------------
|
// ----------------
|
||||||
|
|
||||||
$progress-height: 1rem;
|
$progress-height: 0.625rem;
|
||||||
$progress-background: $medium-gray;
|
$progress-background: $medium-gray;
|
||||||
$progress-margin-bottom: $global-margin;
|
$progress-margin-bottom: $global-margin;
|
||||||
$progress-meter-background: $primary-color;
|
$progress-meter-background: $primary-color;
|
||||||
@@ -504,13 +504,13 @@ $reveal-overlay-background: rgba($black, 0.45);
|
|||||||
// 30. Slider
|
// 30. Slider
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
$slider-width-vertical: 0.5rem;
|
$slider-width-vertical: 0.3125rem;
|
||||||
$slider-transition: all 0.2s ease-in-out;
|
$slider-transition: all 0.2s ease-in-out;
|
||||||
$slider-height: 0.5rem;
|
$slider-height: 0.3125rem;
|
||||||
$slider-background: $light-gray;
|
$slider-background: $light-gray;
|
||||||
$slider-fill-background: $medium-gray;
|
$slider-fill-background: $medium-gray;
|
||||||
$slider-handle-height: 1.4rem;
|
$slider-handle-height: 0.875rem;
|
||||||
$slider-handle-width: 1.4rem;
|
$slider-handle-width: 0.875rem;
|
||||||
$slider-handle-background: $primary-color;
|
$slider-handle-background: $primary-color;
|
||||||
$slider-opacity-disabled: 0.25;
|
$slider-opacity-disabled: 0.25;
|
||||||
$slider-radius: $global-radius;
|
$slider-radius: $global-radius;
|
||||||
@@ -569,7 +569,7 @@ $tab-expand-max: 6;
|
|||||||
$tab-content-background: transparent;
|
$tab-content-background: transparent;
|
||||||
$tab-content-border: transparent;
|
$tab-content-border: transparent;
|
||||||
$tab-content-color: foreground($tab-background, $primary-color);
|
$tab-content-color: foreground($tab-background, $primary-color);
|
||||||
$tab-content-padding: 1rem;
|
$tab-content-padding: 0.625rem;
|
||||||
|
|
||||||
// 34. Thumbnail
|
// 34. Thumbnail
|
||||||
// -------------
|
// -------------
|
||||||
@@ -586,11 +586,11 @@ $thumbnail-radius: $global-radius;
|
|||||||
|
|
||||||
$titlebar-background: $black;
|
$titlebar-background: $black;
|
||||||
$titlebar-color: $white;
|
$titlebar-color: $white;
|
||||||
$titlebar-padding: 0.5rem;
|
$titlebar-padding: 0.3125rem;
|
||||||
$titlebar-text-font-weight: bold;
|
$titlebar-text-font-weight: bold;
|
||||||
$titlebar-icon-color: $white;
|
$titlebar-icon-color: $white;
|
||||||
$titlebar-icon-color-hover: $medium-gray;
|
$titlebar-icon-color-hover: $medium-gray;
|
||||||
$titlebar-icon-spacing: 0.25rem;
|
$titlebar-icon-spacing: 0.15625rem;
|
||||||
|
|
||||||
// 36. Tooltip
|
// 36. Tooltip
|
||||||
// -----------
|
// -----------
|
||||||
@@ -599,19 +599,19 @@ $has-tip-font-weight: $global-weight-bold;
|
|||||||
$has-tip-border-bottom: dotted 1px $dark-gray;
|
$has-tip-border-bottom: dotted 1px $dark-gray;
|
||||||
$tooltip-background-color: $black;
|
$tooltip-background-color: $black;
|
||||||
$tooltip-color: $white;
|
$tooltip-color: $white;
|
||||||
$tooltip-padding: 0.75rem;
|
$tooltip-padding: 0.46875rem;
|
||||||
$tooltip-font-size: $font-size-mini;
|
$tooltip-font-size: $font-size-mini;
|
||||||
$tooltip-pip-width: 0.75rem;
|
$tooltip-pip-width: 0.46875rem;
|
||||||
$tooltip-pip-height: $tooltip-pip-width * 0.866;
|
$tooltip-pip-height: $tooltip-pip-width * 0.866;
|
||||||
$tooltip-radius: $global-radius;
|
$tooltip-radius: $global-radius;
|
||||||
|
|
||||||
// 37. Top Bar
|
// 37. Top Bar
|
||||||
// -----------
|
// -----------
|
||||||
|
|
||||||
$topbar-padding: 0.5rem;
|
$topbar-padding: 0.3125;
|
||||||
$topbar-background: $light-gray;
|
$topbar-background: $light-gray;
|
||||||
$topbar-submenu-background: $topbar-background;
|
$topbar-submenu-background: $topbar-background;
|
||||||
$topbar-title-spacing: 0.5rem 1rem 0.5rem 0;
|
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
|
||||||
$topbar-input-width: 200px;
|
$topbar-input-width: 200px;
|
||||||
$topbar-unstack-breakpoint: medium;
|
$topbar-unstack-breakpoint: medium;
|
||||||
|
|
||||||
|
|||||||
@@ -237,8 +237,8 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin three-column-grid($column-one-width: 25.6rem,
|
@mixin three-column-grid($column-one-width: 16rem,
|
||||||
$column-three-width: 25.6rem) {
|
$column-three-width: 16rem) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -439,7 +439,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
--minus-space-one-point-five: -1.5rem;
|
--minus-space-one-point-five: -0.9375rem;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
transform: translate(
|
transform: translate(
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ a {
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
word-spacing: .12em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
// Font sizes
|
// Font sizes
|
||||||
$font-size-nano: 0.8rem;
|
$font-size-nano: 0.5rem;
|
||||||
$font-size-micro: 1.0rem;
|
$font-size-micro: 0.675rem;
|
||||||
$font-size-mini: 1.2rem;
|
$font-size-mini: 0.75rem;
|
||||||
$font-size-small: 1.4rem;
|
$font-size-small: 0.875rem;
|
||||||
$font-size-default: 1.6rem;
|
$font-size-default: 1rem;
|
||||||
$font-size-medium: 1.8rem;
|
$font-size-medium: 1.125rem;
|
||||||
$font-size-large: 2.2rem;
|
$font-size-large: 1.375rem;
|
||||||
$font-size-big: 2.4rem;
|
$font-size-big: 1.5rem;
|
||||||
$font-size-bigger: 3.0rem;
|
$font-size-bigger: 1.75rem;
|
||||||
$font-size-mega: 3.4rem;
|
$font-size-mega: 2.125rem;
|
||||||
$font-size-giga: 4.0rem;
|
$font-size-giga: 2.5rem;
|
||||||
|
|
||||||
// spaces
|
// spaces
|
||||||
$zero: 0;
|
$zero: 0;
|
||||||
$space-micro: 0.2rem;
|
$space-micro: 0.125rem;
|
||||||
$space-smaller: 0.4rem;
|
$space-smaller: 0.25rem;
|
||||||
$space-small: 0.8rem;
|
$space-small: 0.5rem;
|
||||||
$space-one: 1rem;
|
$space-one: 0.675rem;
|
||||||
$space-slab: 1.2rem;
|
$space-slab: 0.75rem;
|
||||||
$space-normal: 1.6rem;
|
$space-normal: 1rem;
|
||||||
$space-two: 2.0rem;
|
$space-two: 1.25rem;
|
||||||
$space-medium: 2.4rem;
|
$space-medium: 1.5rem;
|
||||||
$space-large: 3.2rem;
|
$space-large: 2rem;
|
||||||
$space-larger: 4.8rem;
|
$space-larger: 3rem;
|
||||||
$space-jumbo: 6.4rem;
|
$space-jumbo: 4rem;
|
||||||
$space-mega: 10.0rem;
|
$space-mega: 6.25rem;
|
||||||
|
|
||||||
// font-weight
|
// font-weight
|
||||||
$font-weight-feather: 100;
|
$font-weight-feather: 100;
|
||||||
@@ -35,8 +35,8 @@ $font-weight-bold: 600;
|
|||||||
$font-weight-black: 700;
|
$font-weight-black: 700;
|
||||||
|
|
||||||
//Navbar
|
//Navbar
|
||||||
$nav-bar-width: 23rem;
|
$nav-bar-width: 14.375rem;
|
||||||
$header-height: 5.6rem;
|
$header-height: 3.5rem;
|
||||||
|
|
||||||
$woot-logo-padding: $space-large $space-two;
|
$woot-logo-padding: $space-large $space-two;
|
||||||
|
|
||||||
@@ -71,20 +71,20 @@ $color-primary-light: #c7e3ff;
|
|||||||
$color-primary-dark: darken($color-woot, 20%);
|
$color-primary-dark: darken($color-woot, 20%);
|
||||||
|
|
||||||
// Thumbnail
|
// Thumbnail
|
||||||
$thumbnail-radius: 4rem;
|
$thumbnail-radius: 2.5rem;
|
||||||
|
|
||||||
// chat-header
|
// chat-header
|
||||||
$conv-header-height: 4rem;
|
$conv-header-height: 2.5rem;
|
||||||
|
|
||||||
// Inbox List
|
// Inbox List
|
||||||
|
|
||||||
$inbox-thumb-size: 4.8rem;
|
$inbox-thumb-size: 3rem;
|
||||||
|
|
||||||
|
|
||||||
// Spinner
|
// Spinner
|
||||||
$spinkit-spinner-color: $color-white !default;
|
$spinkit-spinner-color: $color-white !default;
|
||||||
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
|
$spinkit-spinner-margin: 0 0 0 1rem !default;
|
||||||
$spinkit-size: 1.6rem !default;
|
$spinkit-size: 1rem !default;
|
||||||
|
|
||||||
// Snackbar default
|
// Snackbar default
|
||||||
$woot-snackbar-bg: #323232;
|
$woot-snackbar-bg: #323232;
|
||||||
@@ -101,5 +101,5 @@ $ionicons-font-path: '~ionicons/fonts';
|
|||||||
$transition-ease-in: all 0.250s ease-in;
|
$transition-ease-in: all 0.250s ease-in;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--dashboard-app-tabs-height: 3.9rem;
|
--dashboard-app-tabs-height: 2.4375rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'shared/assets/fonts/inter';
|
@import 'shared/assets/fonts/plus-jakarta';
|
||||||
@import 'shared/assets/stylesheets/animations';
|
@import 'shared/assets/stylesheets/animations';
|
||||||
@import 'shared/assets/stylesheets/colors';
|
@import 'shared/assets/stylesheets/colors';
|
||||||
@import 'shared/assets/stylesheets/spacing';
|
@import 'shared/assets/stylesheets/spacing';
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.multiselect__select {
|
.multiselect__select {
|
||||||
min-height: 4.6rem;
|
min-height: 2.875rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
border: 1px solid var(--s-200);
|
border: 1px solid var(--s-200);
|
||||||
border-color: var(--s-200);
|
border-color: var(--s-200);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 4.4rem;
|
min-height: 2.75rem;
|
||||||
padding-top: $zero;
|
padding-top: $zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
.multiselect__input {
|
.multiselect__input {
|
||||||
@include ghost-input;
|
@include ghost-input;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
height: 4.4rem;
|
height: 2.75rem;
|
||||||
margin-bottom: $zero;
|
margin-bottom: $zero;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.multiselect-wrap--small {
|
.multiselect-wrap--small {
|
||||||
$multiselect-height: 4.0rem;
|
$multiselect-height: 2.5rem;
|
||||||
|
|
||||||
.multiselect__tags,
|
.multiselect__tags,
|
||||||
.multiselect__input {
|
.multiselect__input {
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-height: 3.8rem;
|
max-height: 2.375rem;
|
||||||
padding: var(--space-smaller) var(--space-micro);
|
padding: var(--space-smaller) var(--space-micro);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.multiselect-wrap--medium {
|
.multiselect-wrap--medium {
|
||||||
$multiselect-height: 4.8rem;
|
$multiselect-height: 3rem;
|
||||||
|
|
||||||
.multiselect__tags,
|
.multiselect__tags,
|
||||||
.multiselect__input {
|
.multiselect__input {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import 'shared/assets/fonts/inter';
|
@import 'shared/assets/fonts/inter';
|
||||||
|
@import 'shared/assets/fonts/plus-jakarta';
|
||||||
@import 'shared/assets/stylesheets/animations';
|
@import 'shared/assets/stylesheets/animations';
|
||||||
@import 'shared/assets/stylesheets/colors';
|
@import 'shared/assets/stylesheets/colors';
|
||||||
@import 'shared/assets/stylesheets/spacing';
|
@import 'shared/assets/stylesheets/spacing';
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
@import 'shared/assets/fonts/inter';
|
@import 'shared/assets/fonts/plus-jakarta';
|
||||||
@import '../variables';
|
@import '../variables';
|
||||||
@import '~shared/assets/stylesheets/ionicons';
|
@import '~shared/assets/stylesheets/ionicons';
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
.integration--image {
|
.integration--image {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 10rem;
|
height: 6.25rem;
|
||||||
width: 10rem;
|
width: 6.25rem;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$default-button-height: 4.0rem;
|
$default-button-height: 2.5rem;
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$resolve-button-width: 13.2rem;
|
$resolve-button-width: 8.25rem;
|
||||||
|
|
||||||
// Conversation header - Light BG
|
// Conversation header - Light BG
|
||||||
.conv-header {
|
.conv-header {
|
||||||
@@ -14,12 +14,12 @@ $resolve-button-width: 13.2rem;
|
|||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--space-smaller);
|
border-radius: var(--space-smaller);
|
||||||
margin-right: var(--space-small);
|
margin-right: var(--space-small);
|
||||||
width: 21.6rem;
|
width: 13.5rem;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $medium-gray;
|
color: $medium-gray;
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
line-height: 3.8rem;
|
line-height: 2.375rem;
|
||||||
padding-left: $space-slab;
|
padding-left: $space-slab;
|
||||||
padding-right: $space-smaller;
|
padding-right: $space-smaller;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 27rem;
|
width: 16.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation--meta {
|
.conversation--meta {
|
||||||
|
|||||||
@@ -29,13 +29,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-image {
|
.modal-image {
|
||||||
max-height: 90%;
|
max-height: 76vh;
|
||||||
max-width: 90%;
|
max-width: 76vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-video {
|
.modal-video {
|
||||||
max-height: 75vh;
|
max-height: 76vh;
|
||||||
max-width: 100%;
|
max-width: 76vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@@ -53,16 +53,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video {
|
|
||||||
.modal-container {
|
|
||||||
width: auto;
|
|
||||||
|
|
||||||
.modal--close {
|
|
||||||
z-index: var(--z-index-low);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversations-list-wrap {
|
.conversations-list-wrap {
|
||||||
@@ -283,7 +273,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
--bubble-max-width: 49.6rem;
|
--bubble-max-width: 31rem;
|
||||||
margin: $zero $space-normal;
|
margin: $zero $space-normal;
|
||||||
max-width: Min(var(--bubble-max-width), 84%);
|
max-width: Min(var(--bubble-max-width), 84%);
|
||||||
|
|
||||||
@@ -400,4 +390,3 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
padding: $space-large $space-large $zero;
|
padding: $space-large $space-large $zero;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 6rem;
|
max-height: 3.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@
|
|||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 60rem;
|
width: 37.5rem;
|
||||||
|
|
||||||
&.medium {
|
&.medium {
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
width: 90rem;
|
width: 56.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-box {
|
.content-box {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
// Override min-height : 50px in foundation
|
// Override min-height : 50px in foundation
|
||||||
//
|
//
|
||||||
max-height: $space-mega * 2.4;
|
max-height: $space-mega * 2.4;
|
||||||
min-height: 4.8rem;
|
min-height: 3rem;
|
||||||
padding: var(--space-normal) 0 0;
|
padding: var(--space-normal) 0 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
max-height: $space-mega * 2.4;
|
max-height: $space-mega * 2.4;
|
||||||
// Override min-height : 50px in foundation
|
// Override min-height : 50px in foundation
|
||||||
min-height: 4.8rem;
|
min-height: 3rem;
|
||||||
padding: var(--space-normal) 0 0;
|
padding: var(--space-normal) 0 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-pane {
|
.dropdown-pane {
|
||||||
bottom: 6rem;
|
bottom: 3.75rem;
|
||||||
display: block;
|
display: block;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.ui-snackbar-container {
|
.ui-snackbar-container {
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 40rem;
|
max-width: 25rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
border-radius: $space-smaller;
|
border-radius: $space-smaller;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-bottom: $space-small;
|
margin-bottom: $space-small;
|
||||||
max-width: 40rem;
|
max-width: 25rem;
|
||||||
min-height: 3rem;
|
min-height: 1.875rem;
|
||||||
min-width: 24rem;
|
min-width: 15rem;
|
||||||
padding: $space-slab $space-medium;
|
padding: $space-slab $space-medium;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
.ui-snackbar-action {
|
.ui-snackbar-action {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
padding-left: 3rem;
|
padding-left: 1.875rem;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
|
|||||||
@@ -43,15 +43,15 @@ table {
|
|||||||
|
|
||||||
.woot-thumbnail {
|
.woot-thumbnail {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 5rem;
|
height: 3.125rem;
|
||||||
width: 5rem;
|
width: 3.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-wrapper {
|
.button-wrapper {
|
||||||
@include flex-align(left, null);
|
@include flex-align(left, null);
|
||||||
@include flex;
|
@include flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
min-width: 20rem;
|
min-width: 12.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|||||||
@@ -47,6 +47,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasActiveFolders">
|
<div v-if="hasActiveFolders">
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
icon="edit"
|
||||||
|
@click="onToggleAdvanceFiltersModal"
|
||||||
|
/>
|
||||||
<woot-button
|
<woot-button
|
||||||
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
|
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
|
||||||
size="tiny"
|
size="tiny"
|
||||||
@@ -168,8 +176,11 @@
|
|||||||
v-if="showAdvancedFilters"
|
v-if="showAdvancedFilters"
|
||||||
:initial-filter-types="advancedFilterTypes"
|
:initial-filter-types="advancedFilterTypes"
|
||||||
:initial-applied-filters="appliedFilter"
|
:initial-applied-filters="appliedFilter"
|
||||||
|
:active-folder-name="activeFolderName"
|
||||||
:on-close="closeAdvanceFiltersModal"
|
:on-close="closeAdvanceFiltersModal"
|
||||||
|
:is-folder-view="hasActiveFolders"
|
||||||
@applyFilter="onApplyFilter"
|
@applyFilter="onApplyFilter"
|
||||||
|
@updateFolder="onUpdateSavedFilter"
|
||||||
/>
|
/>
|
||||||
</woot-modal>
|
</woot-modal>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,6 +204,9 @@ import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCust
|
|||||||
import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue';
|
import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import filterMixin from 'shared/mixins/filterMixin';
|
import filterMixin from 'shared/mixins/filterMixin';
|
||||||
|
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||||
|
import countries from 'shared/constants/countries';
|
||||||
|
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hasPressedAltAndJKey,
|
hasPressedAltAndJKey,
|
||||||
@@ -289,6 +303,11 @@ export default {
|
|||||||
appliedFilters: 'getAppliedConversationFilters',
|
appliedFilters: 'getAppliedConversationFilters',
|
||||||
folders: 'customViews/getCustomViews',
|
folders: 'customViews/getCustomViews',
|
||||||
inboxes: 'inboxes/getInboxes',
|
inboxes: 'inboxes/getInboxes',
|
||||||
|
agentList: 'agents/getAgents',
|
||||||
|
teamsList: 'teams/getTeams',
|
||||||
|
inboxesList: 'inboxes/getInboxes',
|
||||||
|
campaigns: 'campaigns/getAllCampaigns',
|
||||||
|
labels: 'labels/getLabels',
|
||||||
}),
|
}),
|
||||||
hasAppliedFilters() {
|
hasAppliedFilters() {
|
||||||
return this.appliedFilters.length !== 0;
|
return this.appliedFilters.length !== 0;
|
||||||
@@ -451,6 +470,9 @@ export default {
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
activeFolderName() {
|
||||||
|
return this.activeFolder?.name;
|
||||||
|
},
|
||||||
activeTeam() {
|
activeTeam() {
|
||||||
if (this.teamId) {
|
if (this.teamId) {
|
||||||
return this.$store.getters['teams/getTeam'](this.teamId);
|
return this.$store.getters['teams/getTeam'](this.teamId);
|
||||||
@@ -483,9 +505,7 @@ export default {
|
|||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
},
|
},
|
||||||
activeFolder() {
|
activeFolder() {
|
||||||
if (!this.hasAppliedFilters) {
|
this.resetAndFetchData();
|
||||||
this.resetAndFetchData();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
chatLists() {
|
chatLists() {
|
||||||
this.chatsOnView = this.conversationList;
|
this.chatsOnView = this.conversationList;
|
||||||
@@ -496,6 +516,10 @@ export default {
|
|||||||
this.$store.dispatch('setChatSortFilter', this.activeSortBy);
|
this.$store.dispatch('setChatSortFilter', this.activeSortBy);
|
||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
|
|
||||||
|
if (this.hasActiveFolders) {
|
||||||
|
this.$store.dispatch('campaigns/get');
|
||||||
|
}
|
||||||
|
|
||||||
bus.$on('fetch_conversation_stats', () => {
|
bus.$on('fetch_conversation_stats', () => {
|
||||||
this.$store.dispatch('conversationStats/get', this.conversationFilters);
|
this.$store.dispatch('conversationStats/get', this.conversationFilters);
|
||||||
});
|
});
|
||||||
@@ -508,6 +532,15 @@ export default {
|
|||||||
this.$store.dispatch('emptyAllConversations');
|
this.$store.dispatch('emptyAllConversations');
|
||||||
this.fetchFilteredConversations(payload);
|
this.fetchFilteredConversations(payload);
|
||||||
},
|
},
|
||||||
|
onUpdateSavedFilter(payload, folderName) {
|
||||||
|
const payloadData = {
|
||||||
|
...this.activeFolder,
|
||||||
|
name: folderName,
|
||||||
|
query: filterQueryGenerator(payload),
|
||||||
|
};
|
||||||
|
this.$store.dispatch('customViews/update', payloadData);
|
||||||
|
this.closeAdvanceFiltersModal();
|
||||||
|
},
|
||||||
onClickOpenAddFoldersModal() {
|
onClickOpenAddFoldersModal() {
|
||||||
this.showAddFoldersModal = true;
|
this.showAddFoldersModal = true;
|
||||||
},
|
},
|
||||||
@@ -521,15 +554,70 @@ export default {
|
|||||||
this.showDeleteFoldersModal = false;
|
this.showDeleteFoldersModal = false;
|
||||||
},
|
},
|
||||||
onToggleAdvanceFiltersModal() {
|
onToggleAdvanceFiltersModal() {
|
||||||
if (!this.hasAppliedFilters) {
|
if (!this.hasAppliedFilters && !this.hasActiveFolders) {
|
||||||
this.initializeExistingFilterToModal();
|
this.initializeExistingFilterToModal();
|
||||||
}
|
}
|
||||||
|
if (this.hasActiveFolders) {
|
||||||
|
this.initializeFolderToFilterModal(this.activeFolder);
|
||||||
|
}
|
||||||
this.showAdvancedFilters = true;
|
this.showAdvancedFilters = true;
|
||||||
},
|
},
|
||||||
closeAdvanceFiltersModal() {
|
closeAdvanceFiltersModal() {
|
||||||
this.showAdvancedFilters = false;
|
this.showAdvancedFilters = false;
|
||||||
this.appliedFilter = [];
|
this.appliedFilter = [];
|
||||||
},
|
},
|
||||||
|
setParamsForEditFolderModal() {
|
||||||
|
// Here we are setting the params for edit folder modal to show the existing values.
|
||||||
|
|
||||||
|
// For agent, team, inboxes,and campaigns we get only the id's from the query.
|
||||||
|
// So we are mapping the id's to the actual values.
|
||||||
|
|
||||||
|
// For labels we get the name of the label from the query.
|
||||||
|
// If we delete the label from the label list then we will not be able to show the label name.
|
||||||
|
|
||||||
|
// For custom attributes we get only attribute key.
|
||||||
|
// So we are mapping it to find the input type of the attribute to show in the edit folder modal.
|
||||||
|
const params = {
|
||||||
|
agents: this.agentList,
|
||||||
|
teams: this.teamsList,
|
||||||
|
inboxes: this.inboxesList,
|
||||||
|
labels: this.labels,
|
||||||
|
campaigns: this.campaigns,
|
||||||
|
languages: languages,
|
||||||
|
countries: countries,
|
||||||
|
filterTypes: advancedFilterTypes,
|
||||||
|
allCustomAttributes: this.$store.getters[
|
||||||
|
'attributes/getAttributesByModel'
|
||||||
|
]('conversation_attribute'),
|
||||||
|
};
|
||||||
|
return params;
|
||||||
|
},
|
||||||
|
initializeFolderToFilterModal(activeFolder) {
|
||||||
|
// Here we are setting the params for edit folder modal.
|
||||||
|
// To show the existing values. when we click on edit folder button.
|
||||||
|
|
||||||
|
// Here we get the query from the active folder.
|
||||||
|
// And we are mapping the query to the actual values.
|
||||||
|
// To show in the edit folder modal by the help of generateValuesForEditCustomViews helper.
|
||||||
|
const query = activeFolder?.query?.payload;
|
||||||
|
if (!Array.isArray(query)) return;
|
||||||
|
|
||||||
|
this.appliedFilter.push(
|
||||||
|
...query.map(filter => ({
|
||||||
|
attribute_key: filter.attribute_key,
|
||||||
|
attribute_model: filter.attribute_model,
|
||||||
|
filter_operator: filter.filter_operator,
|
||||||
|
values: Array.isArray(filter.values)
|
||||||
|
? generateValuesForEditCustomViews(
|
||||||
|
filter,
|
||||||
|
this.setParamsForEditFolderModal()
|
||||||
|
)
|
||||||
|
: [],
|
||||||
|
query_operator: filter.query_operator,
|
||||||
|
custom_attribute_type: filter.custom_attribute_type,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
},
|
||||||
getKeyboardListenerParams() {
|
getKeyboardListenerParams() {
|
||||||
const allConversations = this.$refs.activeConversation.querySelectorAll(
|
const allConversations = this.$refs.activeConversation.querySelectorAll(
|
||||||
'div.conversations-list div.conversation'
|
'div.conversations-list div.conversation'
|
||||||
@@ -575,6 +663,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetAndFetchData() {
|
resetAndFetchData() {
|
||||||
|
this.appliedFilter = [];
|
||||||
this.resetBulkActions();
|
this.resetBulkActions();
|
||||||
this.$store.dispatch('conversationPage/reset');
|
this.$store.dispatch('conversationPage/reset');
|
||||||
this.$store.dispatch('emptyAllConversations');
|
this.$store.dispatch('emptyAllConversations');
|
||||||
@@ -587,7 +676,6 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.fetchConversations();
|
this.fetchConversations();
|
||||||
this.appliedFilter = [];
|
|
||||||
},
|
},
|
||||||
fetchConversations() {
|
fetchConversations() {
|
||||||
this.$store
|
this.$store
|
||||||
@@ -884,7 +972,7 @@ export default {
|
|||||||
|
|
||||||
.conversations-list-wrap {
|
.conversations-list-wrap {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-basis: clamp(32rem, 4vw + 34rem, 44rem);
|
flex-basis: clamp(20rem, 4vw + 21.25rem, 27.5rem);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.hide {
|
&.hide {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import format from 'date-fns/format';
|
import { format, parseISO } from 'date-fns';
|
||||||
import { required, url } from 'vuelidate/lib/validators';
|
import { required, url } from 'vuelidate/lib/validators';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
||||||
@@ -143,15 +143,20 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
formattedValue() {
|
displayValue() {
|
||||||
if (this.isAttributeTypeDate) {
|
if (this.isAttributeTypeDate) {
|
||||||
return format(new Date(this.value || new Date()), DATE_FORMAT);
|
return new Date(this.value || new Date()).toLocaleDateString();
|
||||||
}
|
}
|
||||||
if (this.isAttributeTypeCheckbox) {
|
if (this.isAttributeTypeCheckbox) {
|
||||||
return this.value === 'false' ? false : this.value;
|
return this.value === 'false' ? false : this.value;
|
||||||
}
|
}
|
||||||
return this.value;
|
return this.value;
|
||||||
},
|
},
|
||||||
|
formattedValue() {
|
||||||
|
return this.isAttributeTypeDate
|
||||||
|
? format(this.value ? new Date(this.value) : new Date(), DATE_FORMAT)
|
||||||
|
: this.value;
|
||||||
|
},
|
||||||
listOptions() {
|
listOptions() {
|
||||||
return this.values.map((value, index) => ({
|
return this.values.map((value, index) => ({
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
@@ -192,17 +197,11 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.REQUIRED');
|
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.REQUIRED');
|
||||||
},
|
},
|
||||||
displayValue() {
|
|
||||||
if (this.attributeType === 'date') {
|
|
||||||
return format(new Date(this.editedValue), 'dd-MM-yyyy');
|
|
||||||
}
|
|
||||||
return this.editedValue;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value() {
|
value() {
|
||||||
this.isEditing = false;
|
this.isEditing = false;
|
||||||
this.editedValue = this.value;
|
this.editedValue = this.formattedValue;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -249,9 +248,8 @@ export default {
|
|||||||
onUpdate() {
|
onUpdate() {
|
||||||
const updatedValue =
|
const updatedValue =
|
||||||
this.attributeType === 'date'
|
this.attributeType === 'date'
|
||||||
? format(new Date(this.editedValue), DATE_FORMAT)
|
? parseISO(this.editedValue)
|
||||||
: this.editedValue;
|
: this.editedValue;
|
||||||
|
|
||||||
this.$v.$touch();
|
this.$v.$touch();
|
||||||
if (this.$v.$invalid) {
|
if (this.$v.$invalid) {
|
||||||
return;
|
return;
|
||||||
@@ -345,11 +343,11 @@ export default {
|
|||||||
.error-message {
|
.error-message {
|
||||||
color: var(--r-400);
|
color: var(--r-400);
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 1.4rem;
|
font-size: 0.875rem;
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 0.625rem;
|
||||||
margin-top: -1.6rem;
|
margin-top: -1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div class="column">
|
||||||
|
<woot-modal-header :header-title="$t('CONVERSATION.CUSTOM_SNOOZE.TITLE')" />
|
||||||
|
<form class="row modal-content" @submit.prevent="chooseTime">
|
||||||
|
<date-picker
|
||||||
|
v-model="snoozeTime"
|
||||||
|
type="datetime"
|
||||||
|
inline
|
||||||
|
:lang="lang"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
:disabled-time="disabledTime"
|
||||||
|
:popup-style="{ width: '100%' }"
|
||||||
|
/>
|
||||||
|
<div class="modal-footer justify-content-end w-full">
|
||||||
|
<woot-button variant="clear" @click.prevent="onClose">
|
||||||
|
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button>
|
||||||
|
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.APPLY') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DatePicker from 'vue2-datepicker';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DatePicker,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
snoozeTime: null,
|
||||||
|
lang: {
|
||||||
|
days: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||||
|
yearFormat: 'YYYY',
|
||||||
|
monthFormat: 'MMMM',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onClose() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
chooseTime() {
|
||||||
|
this.$emit('choose-time', this.snoozeTime);
|
||||||
|
},
|
||||||
|
disabledDate(date) {
|
||||||
|
// Disable all the previous dates
|
||||||
|
const yesterday = new Date();
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
return date < yesterday;
|
||||||
|
},
|
||||||
|
disabledTime(date) {
|
||||||
|
// Allow only time after 1 hour
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(now.getHours() + 1);
|
||||||
|
return date < now;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.modal-footer {
|
||||||
|
padding: var(--space-two);
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
padding: var(--space-small) var(--space-two) var(--space-zero);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
>
|
>
|
||||||
<div :class="modalContainerClassName" @click.stop>
|
<div :class="modalContainerClassName" @click.stop>
|
||||||
<woot-button
|
<woot-button
|
||||||
|
v-if="showCloseButton"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
icon="dismiss"
|
icon="dismiss"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
@@ -28,6 +29,10 @@ export default {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
|
showCloseButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
onClose: {
|
onClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -101,7 +106,7 @@ export default {
|
|||||||
.modal-container {
|
.modal-container {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 48rem;
|
width: 30rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modal-big {
|
.modal-big {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default {
|
|||||||
@import '~dashboard/assets/scss/mixins';
|
@import '~dashboard/assets/scss/mixins';
|
||||||
|
|
||||||
.ui-notification-container {
|
.ui-notification-container {
|
||||||
max-width: 40rem;
|
max-width: 25rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: var(--space-normal);
|
right: var(--space-normal);
|
||||||
top: var(--space-normal);
|
top: var(--space-normal);
|
||||||
@@ -94,7 +94,7 @@ export default {
|
|||||||
border-radius: var(--border-radius-medium);
|
border-radius: var(--border-radius-medium);
|
||||||
box-shadow: var(--shadow-large);
|
box-shadow: var(--shadow-large);
|
||||||
|
|
||||||
min-width: 24rem;
|
min-width: 15rem;
|
||||||
padding: var(--space-normal);
|
padding: var(--space-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row settings--section">
|
<div class="row settings--section" :class="{ border: showBorder }">
|
||||||
<div class="medium-4 small-12 title--section">
|
<div class="medium-4 small-12 title--section">
|
||||||
<p class="sub-block-title">
|
<p v-if="title" class="sub-block-title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</p>
|
</p>
|
||||||
<p class="sub-head">
|
<p class="sub-head">
|
||||||
<slot name="subTitle">
|
<slot v-if="subTitle" name="subTitle">
|
||||||
{{ subTitle }}
|
{{ subTitle }}
|
||||||
</slot>
|
</slot>
|
||||||
</p>
|
</p>
|
||||||
@@ -25,11 +25,15 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: '',
|
||||||
},
|
},
|
||||||
subTitle: {
|
subTitle: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: '',
|
||||||
|
},
|
||||||
|
showBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -43,9 +47,13 @@ export default {
|
|||||||
@import '~dashboard/assets/scss/variables';
|
@import '~dashboard/assets/scss/variables';
|
||||||
|
|
||||||
.settings--section {
|
.settings--section {
|
||||||
border-bottom: 1px solid $color-border;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: $space-normal $space-normal $space-normal 0;
|
padding: 0 $space-normal $space-normal 0;
|
||||||
|
|
||||||
|
&.border {
|
||||||
|
padding-top: $space-normal;
|
||||||
|
border-bottom: 1px solid $color-border;
|
||||||
|
}
|
||||||
|
|
||||||
.sub-block-title {
|
.sub-block-title {
|
||||||
color: $color-woot;
|
color: $color-woot;
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<banner
|
||||||
|
v-if="shouldShowBanner"
|
||||||
|
color-scheme="alert"
|
||||||
|
:banner-message="bannerMessage"
|
||||||
|
:action-button-label="actionButtonMessage"
|
||||||
|
has-action-button
|
||||||
|
@click="routeToBilling"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||||
|
import accountMixin from 'dashboard/mixins/account';
|
||||||
|
|
||||||
|
const EMPTY_SUBSCRIPTION_INFO = {
|
||||||
|
status: null,
|
||||||
|
endsOn: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { Banner },
|
||||||
|
mixins: [adminMixin, accountMixin],
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
||||||
|
getAccount: 'accounts/getAccount',
|
||||||
|
}),
|
||||||
|
bannerMessage() {
|
||||||
|
return this.$t('GENERAL_SETTINGS.PAYMENT_PENDING');
|
||||||
|
},
|
||||||
|
actionButtonMessage() {
|
||||||
|
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
|
||||||
|
},
|
||||||
|
shouldShowBanner() {
|
||||||
|
if (!this.isOnChatwootCloud) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isPaymentPending();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
routeToBilling() {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'billing_settings_index',
|
||||||
|
params: { accountId: this.accountId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isPaymentPending() {
|
||||||
|
const { status, endsOn } = this.getSubscriptionInfo();
|
||||||
|
|
||||||
|
if (status && endsOn) {
|
||||||
|
const now = new Date();
|
||||||
|
if (status === 'past_due' && endsOn < now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getSubscriptionInfo() {
|
||||||
|
const account = this.getAccount(this.accountId);
|
||||||
|
if (!account) return EMPTY_SUBSCRIPTION_INFO;
|
||||||
|
|
||||||
|
const { custom_attributes: subscription } = account;
|
||||||
|
if (!subscription) return EMPTY_SUBSCRIPTION_INFO;
|
||||||
|
|
||||||
|
const {
|
||||||
|
subscription_status: status,
|
||||||
|
subscription_ends_on: endsOn,
|
||||||
|
} = subscription;
|
||||||
|
|
||||||
|
return { status, endsOn: new Date(endsOn) };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<banner
|
<banner
|
||||||
v-if="shouldShowBanner"
|
v-if="shouldShowBanner"
|
||||||
class="update-banner"
|
|
||||||
color-scheme="primary"
|
color-scheme="primary"
|
||||||
:banner-message="bannerMessage"
|
:banner-message="bannerMessage"
|
||||||
href-link="https://github.com/chatwoot/chatwoot/releases"
|
href-link="https://github.com/chatwoot/chatwoot/releases"
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<banner
|
||||||
|
v-if="shouldShowBanner"
|
||||||
|
color-scheme="alert"
|
||||||
|
:banner-message="bannerMessage"
|
||||||
|
:action-button-label="actionButtonMessage"
|
||||||
|
has-action-button
|
||||||
|
@click="routeToBilling"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||||
|
import accountMixin from 'dashboard/mixins/account';
|
||||||
|
import { differenceInDays } from 'date-fns';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { Banner },
|
||||||
|
mixins: [adminMixin, accountMixin],
|
||||||
|
data() {
|
||||||
|
return { conversationMeta: {} };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
||||||
|
getAccount: 'accounts/getAccount',
|
||||||
|
}),
|
||||||
|
bannerMessage() {
|
||||||
|
return this.$t('GENERAL_SETTINGS.LIMITS_UPGRADE');
|
||||||
|
},
|
||||||
|
actionButtonMessage() {
|
||||||
|
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
|
||||||
|
},
|
||||||
|
shouldShowBanner() {
|
||||||
|
if (!this.isOnChatwootCloud) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isTrialAccount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isLimitExceeded();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.isOnChatwootCloud) {
|
||||||
|
this.fetchLimits();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchLimits() {
|
||||||
|
this.$store.dispatch('accounts/limits');
|
||||||
|
},
|
||||||
|
routeToBilling() {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'billing_settings_index',
|
||||||
|
params: { accountId: this.accountId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isTrialAccount() {
|
||||||
|
// check if account is less than 15 days old
|
||||||
|
const account = this.getAccount(this.accountId);
|
||||||
|
if (!account) return false;
|
||||||
|
|
||||||
|
const createdAt = new Date(account.created_at);
|
||||||
|
|
||||||
|
const diffDays = differenceInDays(new Date(), createdAt);
|
||||||
|
|
||||||
|
return diffDays <= 15;
|
||||||
|
},
|
||||||
|
isLimitExceeded() {
|
||||||
|
const account = this.getAccount(this.accountId);
|
||||||
|
if (!account) return false;
|
||||||
|
|
||||||
|
const { limits } = account;
|
||||||
|
if (!limits) return false;
|
||||||
|
|
||||||
|
const { conversation, non_web_inboxes: nonWebInboxes } = limits;
|
||||||
|
return this.testLimit(conversation) || this.testLimit(nonWebInboxes);
|
||||||
|
},
|
||||||
|
testLimit({ allowed, consumed }) {
|
||||||
|
return consumed > allowed;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<kbd class="hotkey">
|
||||||
|
<slot />
|
||||||
|
</kbd>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
kbd.hotkey {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--space-micro);
|
||||||
|
min-width: var(--space-normal);
|
||||||
|
font-size: var(--font-size-micro);
|
||||||
|
border-radius: var(--space-smaller);
|
||||||
|
background-color: var(--color-background);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-body);
|
||||||
|
letter-spacing: var(--space-micro);
|
||||||
|
line-height: var(--font-size-micro);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -49,6 +49,17 @@
|
|||||||
class="dropdown-pane dropdown-pane--open"
|
class="dropdown-pane dropdown-pane--open"
|
||||||
>
|
>
|
||||||
<woot-dropdown-menu>
|
<woot-dropdown-menu>
|
||||||
|
<woot-dropdown-item v-if="!isPending">
|
||||||
|
<woot-button
|
||||||
|
variant="clear"
|
||||||
|
color-scheme="secondary"
|
||||||
|
size="small"
|
||||||
|
icon="snooze"
|
||||||
|
@click="() => openSnoozeModal()"
|
||||||
|
>
|
||||||
|
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
|
||||||
|
</woot-button>
|
||||||
|
</woot-dropdown-item>
|
||||||
<woot-dropdown-item v-if="!isPending">
|
<woot-dropdown-item v-if="!isPending">
|
||||||
<woot-button
|
<woot-button
|
||||||
variant="clear"
|
variant="clear"
|
||||||
@@ -60,71 +71,35 @@
|
|||||||
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
|
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</woot-dropdown-item>
|
</woot-dropdown-item>
|
||||||
|
|
||||||
<woot-dropdown-divider v-if="isOpen" />
|
|
||||||
<woot-dropdown-sub-menu
|
|
||||||
v-if="isOpen"
|
|
||||||
:title="this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.TITLE')"
|
|
||||||
>
|
|
||||||
<woot-dropdown-item>
|
|
||||||
<woot-button
|
|
||||||
variant="clear"
|
|
||||||
color-scheme="secondary"
|
|
||||||
size="small"
|
|
||||||
icon="send-clock"
|
|
||||||
@click="() => toggleStatus(STATUS_TYPE.SNOOZED, null)"
|
|
||||||
>
|
|
||||||
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.NEXT_REPLY') }}
|
|
||||||
</woot-button>
|
|
||||||
</woot-dropdown-item>
|
|
||||||
<woot-dropdown-item>
|
|
||||||
<woot-button
|
|
||||||
variant="clear"
|
|
||||||
color-scheme="secondary"
|
|
||||||
size="small"
|
|
||||||
icon="dual-screen-clock"
|
|
||||||
@click="
|
|
||||||
() => toggleStatus(STATUS_TYPE.SNOOZED, snoozeTimes.tomorrow)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.TOMORROW') }}
|
|
||||||
</woot-button>
|
|
||||||
</woot-dropdown-item>
|
|
||||||
<woot-dropdown-item>
|
|
||||||
<woot-button
|
|
||||||
variant="clear"
|
|
||||||
color-scheme="secondary"
|
|
||||||
size="small"
|
|
||||||
icon="calendar-clock"
|
|
||||||
@click="
|
|
||||||
() => toggleStatus(STATUS_TYPE.SNOOZED, snoozeTimes.nextWeek)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.NEXT_WEEK') }}
|
|
||||||
</woot-button>
|
|
||||||
</woot-dropdown-item>
|
|
||||||
</woot-dropdown-sub-menu>
|
|
||||||
</woot-dropdown-menu>
|
</woot-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
|
<woot-modal
|
||||||
|
:show.sync="showCustomSnoozeModal"
|
||||||
|
:on-close="hideCustomSnoozeModal"
|
||||||
|
>
|
||||||
|
<custom-snooze-modal
|
||||||
|
@close="hideCustomSnoozeModal"
|
||||||
|
@choose-time="chooseSnoozeTime"
|
||||||
|
/>
|
||||||
|
</woot-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { getUnixTime } from 'date-fns';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { mixin as clickaway } from 'vue-clickaway';
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import snoozeTimesMixin from 'dashboard/mixins/conversation/snoozeTimesMixin.js';
|
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
import {
|
import {
|
||||||
hasPressedAltAndEKey,
|
hasPressedAltAndEKey,
|
||||||
hasPressedCommandPlusAltAndEKey,
|
hasPressedCommandPlusAltAndEKey,
|
||||||
hasPressedAltAndMKey,
|
hasPressedAltAndMKey,
|
||||||
} from 'shared/helpers/KeyboardHelpers';
|
} from 'shared/helpers/KeyboardHelpers';
|
||||||
|
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
|
||||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||||
import WootDropdownSubMenu from 'shared/components/ui/dropdown/DropdownSubMenu.vue';
|
|
||||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||||
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider';
|
|
||||||
|
|
||||||
import wootConstants from 'dashboard/constants/globals';
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
import {
|
import {
|
||||||
@@ -137,16 +112,16 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
WootDropdownItem,
|
WootDropdownItem,
|
||||||
WootDropdownMenu,
|
WootDropdownMenu,
|
||||||
WootDropdownSubMenu,
|
CustomSnoozeModal,
|
||||||
WootDropdownDivider,
|
|
||||||
},
|
},
|
||||||
mixins: [clickaway, alertMixin, eventListenerMixins, snoozeTimesMixin],
|
mixins: [clickaway, alertMixin, eventListenerMixins],
|
||||||
props: { conversationId: { type: [String, Number], required: true } },
|
props: { conversationId: { type: [String, Number], required: true } },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
showActionsDropdown: false,
|
showActionsDropdown: false,
|
||||||
STATUS_TYPE: wootConstants.STATUS_TYPE,
|
STATUS_TYPE: wootConstants.STATUS_TYPE,
|
||||||
|
showCustomSnoozeModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -218,10 +193,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCmdSnoozeConversation(snoozeType) {
|
onCmdSnoozeConversation(snoozeType) {
|
||||||
this.toggleStatus(
|
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
|
||||||
this.STATUS_TYPE.SNOOZED,
|
this.showCustomSnoozeModal = true;
|
||||||
this.snoozeTimes[snoozeType] || null
|
} else {
|
||||||
);
|
this.toggleStatus(
|
||||||
|
this.STATUS_TYPE.SNOOZED,
|
||||||
|
findSnoozeTime(snoozeType) || null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chooseSnoozeTime(customSnoozeTime) {
|
||||||
|
this.showCustomSnoozeModal = false;
|
||||||
|
if (customSnoozeTime) {
|
||||||
|
this.toggleStatus(
|
||||||
|
this.STATUS_TYPE.SNOOZED,
|
||||||
|
getUnixTime(customSnoozeTime)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCustomSnoozeModal() {
|
||||||
|
this.showCustomSnoozeModal = false;
|
||||||
},
|
},
|
||||||
onCmdOpenConversation() {
|
onCmdOpenConversation() {
|
||||||
this.toggleStatus(this.STATUS_TYPE.OPEN);
|
this.toggleStatus(this.STATUS_TYPE.OPEN);
|
||||||
@@ -252,6 +243,10 @@ export default {
|
|||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
openSnoozeModal() {
|
||||||
|
const ninja = document.querySelector('ninja-keys');
|
||||||
|
ninja.open({ parent: 'snooze_conversation' });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -265,10 +260,14 @@ export default {
|
|||||||
|
|
||||||
.dropdown-pane {
|
.dropdown-pane {
|
||||||
left: unset;
|
left: unset;
|
||||||
top: 4.2rem;
|
top: 2.625rem;
|
||||||
margin-top: var(--space-micro);
|
margin-top: var(--space-micro);
|
||||||
right: 0;
|
right: 0;
|
||||||
max-width: 20rem;
|
max-width: 12.5rem;
|
||||||
min-width: 15.6rem;
|
min-width: 9.75rem;
|
||||||
|
|
||||||
|
.dropdown-menu__item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export default {
|
|||||||
|
|
||||||
.account-selector--modal {
|
.account-selector--modal {
|
||||||
.modal-container {
|
.modal-container {
|
||||||
width: 40rem;
|
width: 25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 6rem;
|
height: 3.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-user--options {
|
.current-user--options {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
:href="href"
|
:href="href"
|
||||||
class="button small clear secondary"
|
class="button small clear secondary custom-sidebar--button"
|
||||||
:class="{ 'is-active': isActive }"
|
:class="{ 'is-active': isActive }"
|
||||||
@click="e => handleProfileSettingClick(e, navigate)"
|
@click="e => handleProfileSettingClick(e, navigate)"
|
||||||
>
|
>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<woot-dropdown-item v-if="currentUser.type === 'SuperAdmin'">
|
<woot-dropdown-item v-if="currentUser.type === 'SuperAdmin'">
|
||||||
<a
|
<a
|
||||||
href="/super_admin"
|
href="/super_admin"
|
||||||
class="button small clear secondary"
|
class="button small clear secondary custom-sidebar--button"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener nofollow noreferrer"
|
rel="noopener nofollow noreferrer"
|
||||||
@click="$emit('close')"
|
@click="$emit('close')"
|
||||||
@@ -157,4 +157,9 @@ export default {
|
|||||||
top: unset;
|
top: unset;
|
||||||
z-index: var(--z-index-low);
|
z-index: var(--z-index-low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-sidebar--button {
|
||||||
|
height: var(--space-large) !important;
|
||||||
|
padding: var(--space-smaller) 0.675rem !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ export default {
|
|||||||
.map(view => ({
|
.map(view => ({
|
||||||
id: view.id,
|
id: view.id,
|
||||||
label: view.name,
|
label: view.name,
|
||||||
|
count: view.count,
|
||||||
truncateLabel: true,
|
truncateLabel: true,
|
||||||
toState: frontendURL(
|
toState: frontendURL(
|
||||||
`accounts/${this.accountId}/custom_view/${view.id}`
|
`accounts/${this.accountId}/custom_view/${view.id}`
|
||||||
@@ -211,6 +212,7 @@ export default {
|
|||||||
.map(view => ({
|
.map(view => ({
|
||||||
id: view.id,
|
id: view.id,
|
||||||
label: view.name,
|
label: view.name,
|
||||||
|
count: view.count,
|
||||||
truncateLabel: true,
|
truncateLabel: true,
|
||||||
toState: frontendURL(
|
toState: frontendURL(
|
||||||
`accounts/${this.accountId}/contacts/custom_view/${view.id}`
|
`accounts/${this.accountId}/contacts/custom_view/${view.id}`
|
||||||
@@ -258,7 +260,7 @@ export default {
|
|||||||
background: var(--white);
|
background: var(--white);
|
||||||
border-right: 1px solid var(--s-50);
|
border-right: 1px solid var(--s-50);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 20rem;
|
width: 12.5rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
position: unset;
|
position: unset;
|
||||||
|
|||||||
+30
-16
@@ -20,19 +20,22 @@
|
|||||||
class="badge--label"
|
class="badge--label"
|
||||||
:style="{ backgroundColor: labelColor }"
|
:style="{ backgroundColor: labelColor }"
|
||||||
/>
|
/>
|
||||||
<span
|
<div class="menu-label--wrapper">
|
||||||
:title="menuTitle"
|
<span
|
||||||
class="menu-label button__content"
|
:title="menuTitle"
|
||||||
:class="{ 'text-truncate': shouldTruncate }"
|
class="menu-label button__content"
|
||||||
>
|
:class="{ 'text-truncate': shouldTruncate }"
|
||||||
{{ label }}
|
>
|
||||||
<span v-if="showChildCount" class="count-view">
|
{{ label }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="showChildCount"
|
||||||
|
class="count-view"
|
||||||
|
:class="isCountZero ? 'text-muted' : 'text-color'"
|
||||||
|
>
|
||||||
{{ childItemCount }}
|
{{ childItemCount }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
<span v-if="count" class="badge" :class="{ secondary: !isActive }">
|
|
||||||
{{ count }}
|
|
||||||
</span>
|
|
||||||
<span v-if="warningIcon" class="badge--icon">
|
<span v-if="warningIcon" class="badge--icon">
|
||||||
<fluent-icon
|
<fluent-icon
|
||||||
v-tooltip.top-end="$t('SIDEBAR.FACEBOOK_REAUTHORIZE')"
|
v-tooltip.top-end="$t('SIDEBAR.FACEBOOK_REAUTHORIZE')"
|
||||||
@@ -72,10 +75,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
count: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
showChildCount: {
|
showChildCount: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -89,6 +88,9 @@ export default {
|
|||||||
showIcon() {
|
showIcon() {
|
||||||
return { 'text-truncate': this.shouldTruncate };
|
return { 'text-truncate': this.shouldTruncate };
|
||||||
},
|
},
|
||||||
|
isCountZero() {
|
||||||
|
return this.childItemCount === 0;
|
||||||
|
},
|
||||||
menuTitle() {
|
menuTitle() {
|
||||||
return this.shouldTruncate ? this.label : '';
|
return this.shouldTruncate ? this.label : '';
|
||||||
},
|
},
|
||||||
@@ -134,6 +136,15 @@ $label-badge-size: var(--space-slab);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-label--wrapper {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-label {
|
.menu-label {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@@ -174,11 +185,14 @@ $label-badge-size: var(--space-slab);
|
|||||||
.count-view {
|
.count-view {
|
||||||
background: var(--s-50);
|
background: var(--s-50);
|
||||||
border-radius: var(--border-radius-normal);
|
border-radius: var(--border-radius-normal);
|
||||||
color: var(--s-600);
|
|
||||||
font-size: var(--font-size-micro);
|
font-size: var(--font-size-micro);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
margin-left: var(--space-smaller);
|
margin-left: var(--space-smaller);
|
||||||
padding: var(--space-zero) var(--space-smaller);
|
padding: var(--space-zero) var(--space-smaller);
|
||||||
line-height: var(--font-size-small);
|
line-height: var(--font-size-small);
|
||||||
|
|
||||||
|
&.text-color {
|
||||||
|
color: var(--s-600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.announcement-popup {
|
.announcement-popup {
|
||||||
max-width: 24rem;
|
max-width: 15rem;
|
||||||
min-width: 16rem;
|
min-width: 10rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-if="showSeparator" class="separator">
|
|
||||||
OR
|
|
||||||
</div>
|
|
||||||
<a :href="getGoogleAuthUrl()">
|
|
||||||
<button
|
|
||||||
class="button expanded button__google_login"
|
|
||||||
:class="{
|
|
||||||
// Explicit checking to ensure no other value is used
|
|
||||||
large: buttonSize === 'large',
|
|
||||||
small: buttonSize === 'small',
|
|
||||||
tiny: buttonSize === 'tiny',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/assets/images/auth/google.svg"
|
|
||||||
alt="Google Logo"
|
|
||||||
class="icon"
|
|
||||||
/>
|
|
||||||
<slot>{{ $t('LOGIN.OAUTH.GOOGLE_LOGIN') }}</slot>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const validButtonSizes = ['small', 'tiny', 'large'];
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
showSeparator: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
buttonSize: {
|
|
||||||
type: String,
|
|
||||||
default: undefined,
|
|
||||||
validator: value =>
|
|
||||||
validButtonSizes.includes(value) || value === undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getGoogleAuthUrl() {
|
|
||||||
// Ideally a request to /auth/google_oauth2 should be made
|
|
||||||
// Creating the URL manually because the devise-token-auth with
|
|
||||||
// omniauth has a standing issue on redirecting the post request
|
|
||||||
// https://github.com/lynndylanhurley/devise_token_auth/issues/1466
|
|
||||||
const baseUrl =
|
|
||||||
'https://accounts.google.com/o/oauth2/auth/oauthchooseaccount';
|
|
||||||
const clientId = window.chatwootConfig.googleOAuthClientId;
|
|
||||||
const redirectUri = window.chatwootConfig.googleOAuthCallbackUrl;
|
|
||||||
const responseType = 'code';
|
|
||||||
const scope = 'email profile';
|
|
||||||
|
|
||||||
// Build the query string
|
|
||||||
const queryString = new URLSearchParams({
|
|
||||||
client_id: clientId,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
response_type: responseType,
|
|
||||||
scope: scope,
|
|
||||||
}).toString();
|
|
||||||
|
|
||||||
// Construct the full URL
|
|
||||||
return `${baseUrl}?${queryString}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.separator {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: var(--space-two) var(--space-zero);
|
|
||||||
gap: var(--space-one);
|
|
||||||
color: var(--s-300);
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
flex: 1;
|
|
||||||
height: 1px;
|
|
||||||
background: var(--s-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button__google_login {
|
|
||||||
background: var(--white);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: var(--space-one);
|
|
||||||
border: 1px solid var(--s-100);
|
|
||||||
color: var(--b-800);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -11,26 +11,29 @@
|
|||||||
{{ hrefLinkText }}
|
{{ hrefLinkText }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<woot-button
|
<div class="actions">
|
||||||
v-if="hasActionButton"
|
<woot-button
|
||||||
size="small"
|
v-if="hasActionButton"
|
||||||
variant="link"
|
size="tiny"
|
||||||
icon="arrow-right"
|
icon="arrow-right"
|
||||||
color-scheme="primary"
|
:variant="actionButtonVariant"
|
||||||
class-names="banner-action__button"
|
color-scheme="primary"
|
||||||
@click="onClick"
|
class-names="banner-action__button"
|
||||||
>
|
@click="onClick"
|
||||||
{{ actionButtonLabel }}
|
>
|
||||||
</woot-button>
|
{{ actionButtonLabel }}
|
||||||
<woot-button
|
</woot-button>
|
||||||
v-if="hasCloseButton"
|
<woot-button
|
||||||
size="small"
|
v-if="hasCloseButton"
|
||||||
variant="link"
|
size="tiny"
|
||||||
color-scheme="secondary"
|
:color-scheme="colorScheme"
|
||||||
icon="dismiss-circle"
|
icon="dismiss-circle"
|
||||||
class-names="banner-action__button"
|
class-names="banner-action__button"
|
||||||
@click="onClickClose"
|
@click="onClickClose"
|
||||||
/>
|
>
|
||||||
|
{{ $t('GENERAL_SETTINGS.DISMISS') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -53,6 +56,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
actionButtonVariant: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
actionButtonLabel: {
|
actionButtonLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
@@ -68,7 +75,12 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bannerClasses() {
|
bannerClasses() {
|
||||||
return [this.colorScheme];
|
const classList = [this.colorScheme];
|
||||||
|
|
||||||
|
if (this.hasActionButton || this.hasCloseButton) {
|
||||||
|
classList.push('has-button');
|
||||||
|
}
|
||||||
|
return classList;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -84,17 +96,26 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.banner {
|
.banner {
|
||||||
|
--x-padding: var(--space-normal);
|
||||||
|
--y-padding: var(--space-slab);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: var(--x-padding);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
padding: var(--space-slab) var(--space-normal);
|
padding: var(--y-padding) var(--x-padding);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: sticky;
|
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
background: var(--w-500);
|
background: var(--w-500);
|
||||||
.banner-action__button {
|
.banner-action__button {
|
||||||
|
background: var(--w-600);
|
||||||
|
border: none;
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--w-800);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +128,16 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.alert {
|
&.alert {
|
||||||
background: var(--r-400);
|
background: var(--r-500);
|
||||||
|
.banner-action__button {
|
||||||
|
background: var(--r-700);
|
||||||
|
border: none;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--r-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.warning {
|
&.warning {
|
||||||
@@ -133,8 +163,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.banner-action__button {
|
.banner-action__button {
|
||||||
margin: 0 var(--space-smaller);
|
|
||||||
|
|
||||||
::v-deep .button__content {
|
::v-deep .button__content {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -144,5 +172,11 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-smaller);
|
||||||
|
right: var(--y-padding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
<div class="content-wrap">
|
<div class="content-wrap">
|
||||||
{{ content }}
|
{{ content }}
|
||||||
</div>
|
</div>
|
||||||
<div class="image-wrap">
|
<div v-if="src" class="image-wrap">
|
||||||
<img :src="src" class="image" :class="{ activeimage: active }" />
|
<img :src="src" class="image" :class="{ activeimage: active }" />
|
||||||
</div>
|
</div>
|
||||||
|
<slot v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -52,9 +53,9 @@ export default {
|
|||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 34rem;
|
max-height: 21.25rem;
|
||||||
max-width: 38rem;
|
max-width: 23.75rem;
|
||||||
min-width: 24rem;
|
min-width: 15rem;
|
||||||
|
|
||||||
.header--wrap {
|
.header--wrap {
|
||||||
background: var(--s-50);
|
background: var(--s-50);
|
||||||
@@ -62,7 +63,7 @@ export default {
|
|||||||
border-top-left-radius: var(--border-radius-normal);
|
border-top-left-radius: var(--border-radius-normal);
|
||||||
border-top-right-radius: var(--border-radius-normal);
|
border-top-right-radius: var(--border-radius-normal);
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 4rem;
|
height: 2.5rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: var(--space-small);
|
padding: var(--space-small);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
--space-one-point-five: 1.5rem;
|
--space-one-point-five: 0.9375rem;
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
box-shadow: var(--toggle-button-box-shadow);
|
box-shadow: var(--toggle-button-box-shadow);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin-top: var(--space-normal);
|
margin-top: var(--space-normal);
|
||||||
max-height: 20rem;
|
max-height: 12.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-item {
|
.preview-item {
|
||||||
@@ -84,7 +84,7 @@ export default {
|
|||||||
background: var(--color-background-light);
|
background: var(--color-background-light);
|
||||||
background: var(--b-50);
|
background: var(--b-50);
|
||||||
border-radius: var(--border-radius-normal);
|
border-radius: var(--border-radius-normal);
|
||||||
width: 24rem;
|
width: 15rem;
|
||||||
padding: var(--space-smaller);
|
padding: var(--space-smaller);
|
||||||
margin-bottom: var(--space-one);
|
margin-bottom: var(--space-one);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
.option-item--inbox {
|
.option-item--inbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 3.8rem;
|
height: 2.375rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0 var(--space-smaller);
|
padding: 0 var(--space-smaller);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
v-if="showSearchDropdownLabel"
|
v-if="showSearchDropdownLabel"
|
||||||
:account-labels="allLabels"
|
:account-labels="allLabels"
|
||||||
:selected-labels="selectedLabels"
|
:selected-labels="selectedLabels"
|
||||||
|
:allow-creation="isAdmin"
|
||||||
@add="addItem"
|
@add="addItem"
|
||||||
@remove="removeItem"
|
@remove="removeItem"
|
||||||
/>
|
/>
|
||||||
@@ -30,8 +31,15 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||||
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
||||||
import { mixin as clickaway } from 'vue-clickaway';
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
|
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||||
|
import {
|
||||||
|
buildHotKeys,
|
||||||
|
isEscape,
|
||||||
|
isActiveElementTypeable,
|
||||||
|
} from 'shared/helpers/KeyboardHelpers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -39,7 +47,7 @@ export default {
|
|||||||
LabelDropdown,
|
LabelDropdown,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [clickaway],
|
mixins: [clickaway, adminMixin, eventListenerMixins],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
allLabels: {
|
allLabels: {
|
||||||
@@ -80,6 +88,18 @@ export default {
|
|||||||
closeDropdownLabel() {
|
closeDropdownLabel() {
|
||||||
this.showSearchDropdownLabel = false;
|
this.showSearchDropdownLabel = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleKeyEvents(e) {
|
||||||
|
const keyPattern = buildHotKeys(e);
|
||||||
|
|
||||||
|
if (keyPattern === 'l' && !isActiveElementTypeable(e)) {
|
||||||
|
this.toggleLabels();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (isEscape(e) && this.showSearchDropdownLabel) {
|
||||||
|
this.closeDropdownLabel();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.audio-wave-wrapper {
|
.audio-wave-wrapper {
|
||||||
min-height: 8rem;
|
min-height: 5rem;
|
||||||
height: 8rem;
|
height: 5rem;
|
||||||
}
|
}
|
||||||
.video-js .vjs-control-bar {
|
.video-js .vjs-control-bar {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|||||||
@@ -468,8 +468,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror-woot-style {
|
.ProseMirror-woot-style {
|
||||||
min-height: 8rem;
|
min-height: 5rem;
|
||||||
max-height: 12rem;
|
max-height: 7.5rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror-woot-style {
|
.ProseMirror-woot-style {
|
||||||
min-height: 8rem;
|
min-height: 5rem;
|
||||||
max-height: 12rem;
|
max-height: 7.5rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +241,6 @@ export default {
|
|||||||
box-shadow: var(--shadow-large);
|
box-shadow: var(--shadow-large);
|
||||||
border-radius: var(--border-radius-normal);
|
border-radius: var(--border-radius-normal);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
min-width: 40rem;
|
min-width: 25rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -365,6 +365,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 8rem;
|
font-size: 5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ export default {
|
|||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
collection() {
|
||||||
|
this.renderChart(this.collection, {
|
||||||
|
...chartOptions,
|
||||||
|
...this.chartOptions,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderChart(this.collection, {
|
this.renderChart(this.collection, {
|
||||||
...chartOptions,
|
...chartOptions,
|
||||||
|
|||||||
+79
-11
@@ -1,9 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<woot-modal-header :header-title="$t('FILTER.TITLE')">
|
<woot-modal-header :header-title="filterModalHeaderTitle">
|
||||||
<p>{{ $t('FILTER.SUBTITLE') }}</p>
|
<p>{{ filterModalSubTitle }}</p>
|
||||||
</woot-modal-header>
|
</woot-modal-header>
|
||||||
<div class="row modal-content">
|
<div class="column modal-content">
|
||||||
|
<div v-if="isFolderView" class="columns">
|
||||||
|
<label class="input-label" :class="{ error: !activeFolderNewName }">
|
||||||
|
{{ $t('FILTER.FOLDER_LABEL') }}
|
||||||
|
<input v-model="activeFolderNewName" type="text" class="name-input" />
|
||||||
|
<span v-if="!activeFolderNewName" class="message">
|
||||||
|
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="input-label">
|
||||||
|
{{ $t('FILTER.FOLDER_QUERY_LABEL') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="medium-12 columns filters-wrap">
|
<div class="medium-12 columns filters-wrap">
|
||||||
<filter-input-box
|
<filter-input-box
|
||||||
v-for="(filter, i) in appliedFilters"
|
v-for="(filter, i) in appliedFilters"
|
||||||
@@ -42,7 +54,14 @@
|
|||||||
<woot-button class="button clear" @click.prevent="onClose">
|
<woot-button class="button clear" @click.prevent="onClose">
|
||||||
{{ $t('FILTER.CANCEL_BUTTON_LABEL') }}
|
{{ $t('FILTER.CANCEL_BUTTON_LABEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
<woot-button @click="submitFilterQuery">
|
<woot-button
|
||||||
|
v-if="isFolderView"
|
||||||
|
:disabled="!activeFolderNewName"
|
||||||
|
@click="updateSavedCustomViews"
|
||||||
|
>
|
||||||
|
{{ $t('FILTER.UPDATE_BUTTON_LABEL') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button v-else @click="submitFilterQuery">
|
||||||
{{ $t('FILTER.SUBMIT_BUTTON_LABEL') }}
|
{{ $t('FILTER.SUBMIT_BUTTON_LABEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,6 +100,14 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
activeFolderName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
isFolderView: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
appliedFilters: {
|
appliedFilters: {
|
||||||
@@ -107,6 +134,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
show: true,
|
show: true,
|
||||||
appliedFilters: this.initialAppliedFilters,
|
appliedFilters: this.initialAppliedFilters,
|
||||||
|
activeFolderNewName: this.activeFolderName,
|
||||||
filterTypes: this.initialFilterTypes,
|
filterTypes: this.initialFilterTypes,
|
||||||
filterAttributeGroups,
|
filterAttributeGroups,
|
||||||
filterGroups: [],
|
filterGroups: [],
|
||||||
@@ -119,6 +147,16 @@ export default {
|
|||||||
...mapGetters({
|
...mapGetters({
|
||||||
getAppliedConversationFilters: 'getAppliedConversationFilters',
|
getAppliedConversationFilters: 'getAppliedConversationFilters',
|
||||||
}),
|
}),
|
||||||
|
filterModalHeaderTitle() {
|
||||||
|
return !this.isFolderView
|
||||||
|
? this.$t('FILTER.TITLE')
|
||||||
|
: this.$t('FILTER.EDIT_CUSTOM_FILTER');
|
||||||
|
},
|
||||||
|
filterModalSubTitle() {
|
||||||
|
return !this.isFolderView
|
||||||
|
? this.$t('FILTER.SUBTITLE')
|
||||||
|
: this.$t('FILTER.CUSTOM_VIEWS_SUBTITLE');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setFilterAttributes();
|
this.setFilterAttributes();
|
||||||
@@ -126,7 +164,7 @@ export default {
|
|||||||
if (this.getAppliedConversationFilters.length) {
|
if (this.getAppliedConversationFilters.length) {
|
||||||
this.appliedFilters = [];
|
this.appliedFilters = [];
|
||||||
this.appliedFilters = [...this.getAppliedConversationFilters];
|
this.appliedFilters = [...this.getAppliedConversationFilters];
|
||||||
} else {
|
} else if (!this.isFolderView) {
|
||||||
this.appliedFilters.push({
|
this.appliedFilters.push({
|
||||||
attribute_key: 'status',
|
attribute_key: 'status',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
@@ -177,11 +215,11 @@ export default {
|
|||||||
if (key === 'created_at' || key === 'last_activity_at')
|
if (key === 'created_at' || key === 'last_activity_at')
|
||||||
if (operator === 'days_before') return 'plain_text';
|
if (operator === 'days_before') return 'plain_text';
|
||||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||||
return type.inputType;
|
return type?.inputType;
|
||||||
},
|
},
|
||||||
getOperators(key) {
|
getOperators(key) {
|
||||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||||
return type.filterOperators;
|
return type?.filterOperators;
|
||||||
},
|
},
|
||||||
getDropdownValues(type) {
|
getDropdownValues(type) {
|
||||||
const statusFilters = this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
|
const statusFilters = this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
|
||||||
@@ -267,11 +305,30 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
appendNewFilter() {
|
appendNewFilter() {
|
||||||
this.appliedFilters.push({
|
if (this.isFolderView) {
|
||||||
attribute_key: 'status',
|
this.setQueryOperatorOnLastQuery();
|
||||||
filter_operator: 'equal_to',
|
} else {
|
||||||
values: '',
|
this.appliedFilters.push({
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setQueryOperatorOnLastQuery() {
|
||||||
|
const lastItemIndex = this.appliedFilters.length - 1;
|
||||||
|
this.appliedFilters[lastItemIndex] = {
|
||||||
|
...this.appliedFilters[lastItemIndex],
|
||||||
query_operator: 'and',
|
query_operator: 'and',
|
||||||
|
};
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.appliedFilters.push({
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
removeFilter(index) {
|
removeFilter(index) {
|
||||||
@@ -296,6 +353,9 @@ export default {
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateSavedCustomViews() {
|
||||||
|
this.$emit('updateFolder', this.appliedFilters, this.activeFolderNewName);
|
||||||
|
},
|
||||||
resetFilter(index, currentFilter) {
|
resetFilter(index, currentFilter) {
|
||||||
this.appliedFilters[index].filter_operator = this.filterTypes.find(
|
this.appliedFilters[index].filter_operator = this.filterTypes.find(
|
||||||
filter => filter.attributeKey === currentFilter.attribute_key
|
filter => filter.attributeKey === currentFilter.attribute_key
|
||||||
@@ -322,4 +382,12 @@ export default {
|
|||||||
.filter-actions {
|
.filter-actions {
|
||||||
margin-top: var(--space-normal);
|
margin-top: var(--space-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
margin-bottom: var(--space-smaller);
|
||||||
|
|
||||||
|
.name-input {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ export default {
|
|||||||
margin-top: var(--space-smaller);
|
margin-top: var(--space-smaller);
|
||||||
padding: var(--space-normal);
|
padding: var(--space-normal);
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 21rem;
|
width: 13.125rem;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
|
|||||||
@@ -176,23 +176,23 @@ export default {
|
|||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
|
|
||||||
@include breakpoint(medium up) {
|
@include breakpoint(medium up) {
|
||||||
flex-basis: 28rem;
|
flex-basis: 17.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint(large up) {
|
@include breakpoint(large up) {
|
||||||
flex-basis: 30em;
|
flex-basis: 18.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint(xlarge up) {
|
@include breakpoint(xlarge up) {
|
||||||
flex-basis: 31em;
|
flex-basis: 19.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint(xxlarge up) {
|
@include breakpoint(xxlarge up) {
|
||||||
flex-basis: 33rem;
|
flex-basis: 20.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include breakpoint(xxxlarge up) {
|
@include breakpoint(xxxlarge up) {
|
||||||
flex-basis: 40rem;
|
flex-basis: 25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::v-deep .contact--panel {
|
&::v-deep .contact--panel {
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import agentMixin from '../../../mixins/agentMixin.js';
|
import agentMixin from '../../../mixins/agentMixin.js';
|
||||||
import BackButton from '../BackButton';
|
import BackButton from '../BackButton';
|
||||||
import differenceInHours from 'date-fns/differenceInHours';
|
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import InboxName from '../InboxName';
|
import InboxName from '../InboxName';
|
||||||
@@ -65,6 +64,8 @@ import MoreActions from './MoreActions';
|
|||||||
import Thumbnail from '../Thumbnail';
|
import Thumbnail from '../Thumbnail';
|
||||||
import wootConstants from 'dashboard/constants/globals';
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
|
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
|
||||||
|
import { conversationReopenTime } from 'dashboard/helper/snoozeHelpers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BackButton,
|
BackButton,
|
||||||
@@ -125,17 +126,9 @@ export default {
|
|||||||
snoozedDisplayText() {
|
snoozedDisplayText() {
|
||||||
const { snoozed_until: snoozedUntil } = this.currentChat;
|
const { snoozed_until: snoozedUntil } = this.currentChat;
|
||||||
if (snoozedUntil) {
|
if (snoozedUntil) {
|
||||||
// When the snooze is applied, it schedules the unsnooze event to next day/week 9AM.
|
return `${this.$t(
|
||||||
// By that logic if the time difference is less than or equal to 24 + 9 hours we can consider it tomorrow.
|
'CONVERSATION.HEADER.SNOOZED_UNTIL'
|
||||||
const MAX_TIME_DIFFERENCE = 33;
|
)} ${conversationReopenTime(snoozedUntil)}`;
|
||||||
const isSnoozedUntilTomorrow =
|
|
||||||
differenceInHours(new Date(snoozedUntil), new Date()) <=
|
|
||||||
MAX_TIME_DIFFERENCE;
|
|
||||||
return this.$t(
|
|
||||||
isSnoozedUntilTomorrow
|
|
||||||
? 'CONVERSATION.HEADER.SNOOZED_UNTIL_TOMORROW'
|
|
||||||
: 'CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_WEEK'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
|
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default {
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
margin: var(--space-normal);
|
margin: var(--space-normal);
|
||||||
width: 10rem;
|
width: 6.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|||||||
@@ -25,10 +25,14 @@
|
|||||||
<blockquote v-if="storyReply" class="story-reply-quote">
|
<blockquote v-if="storyReply" class="story-reply-quote">
|
||||||
<span>{{ $t('CONVERSATION.REPLIED_TO_STORY') }}</span>
|
<span>{{ $t('CONVERSATION.REPLIED_TO_STORY') }}</span>
|
||||||
<bubble-image
|
<bubble-image
|
||||||
v-if="!hasStoryError"
|
v-if="!hasImgStoryError && storyUrl"
|
||||||
:url="storyUrl"
|
:url="storyUrl"
|
||||||
@error="onStoryLoadError"
|
@error="onStoryLoadError"
|
||||||
/>
|
/>
|
||||||
|
<bubble-video
|
||||||
|
v-else-if="hasImgStoryError && storyUrl"
|
||||||
|
:url="storyUrl"
|
||||||
|
/>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<bubble-text
|
<bubble-text
|
||||||
v-if="data.content"
|
v-if="data.content"
|
||||||
@@ -48,23 +52,12 @@
|
|||||||
{{ $t('CONVERSATION.UPLOADING_ATTACHMENTS') }}
|
{{ $t('CONVERSATION.UPLOADING_ATTACHMENTS') }}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="!isPending && hasAttachments">
|
<div v-if="!isPending && hasAttachments">
|
||||||
<div v-for="attachment in data.attachments" :key="attachment.id">
|
<div v-for="attachment in attachments" :key="attachment.id">
|
||||||
<bubble-image
|
<bubble-image-audio-video
|
||||||
v-if="attachment.file_type === 'image' && !hasImageError"
|
v-if="isAttachmentImageVideoAudio(attachment.file_type)"
|
||||||
:url="attachment.data_url"
|
:attachment="attachment"
|
||||||
@error="onImageLoadError"
|
@error="onImageLoadError"
|
||||||
/>
|
/>
|
||||||
<audio
|
|
||||||
v-else-if="attachment.file_type === 'audio'"
|
|
||||||
controls
|
|
||||||
class="skip-context-menu"
|
|
||||||
>
|
|
||||||
<source :src="`${attachment.data_url}?t=${Date.now()}`" />
|
|
||||||
</audio>
|
|
||||||
<bubble-video
|
|
||||||
v-else-if="attachment.file_type === 'video'"
|
|
||||||
:url="attachment.data_url"
|
|
||||||
/>
|
|
||||||
<bubble-location
|
<bubble-location
|
||||||
v-else-if="attachment.file_type === 'location'"
|
v-else-if="attachment.file_type === 'location'"
|
||||||
:latitude="attachment.coordinates_lat"
|
:latitude="attachment.coordinates_lat"
|
||||||
@@ -140,11 +133,12 @@ import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
|||||||
import BubbleActions from './bubble/Actions';
|
import BubbleActions from './bubble/Actions';
|
||||||
import BubbleFile from './bubble/File';
|
import BubbleFile from './bubble/File';
|
||||||
import BubbleImage from './bubble/Image';
|
import BubbleImage from './bubble/Image';
|
||||||
|
import BubbleVideo from './bubble/Video';
|
||||||
|
import BubbleImageAudioVideo from './bubble/ImageAudioVideo';
|
||||||
import BubbleIntegration from './bubble/Integration.vue';
|
import BubbleIntegration from './bubble/Integration.vue';
|
||||||
import BubbleLocation from './bubble/Location';
|
import BubbleLocation from './bubble/Location';
|
||||||
import BubbleMailHead from './bubble/MailHead';
|
import BubbleMailHead from './bubble/MailHead';
|
||||||
import BubbleText from './bubble/Text';
|
import BubbleText from './bubble/Text';
|
||||||
import BubbleVideo from './bubble/Video.vue';
|
|
||||||
import BubbleContact from './bubble/Contact';
|
import BubbleContact from './bubble/Contact';
|
||||||
import Spinner from 'shared/components/Spinner';
|
import Spinner from 'shared/components/Spinner';
|
||||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||||
@@ -161,11 +155,12 @@ export default {
|
|||||||
BubbleActions,
|
BubbleActions,
|
||||||
BubbleFile,
|
BubbleFile,
|
||||||
BubbleImage,
|
BubbleImage,
|
||||||
|
BubbleVideo,
|
||||||
|
BubbleImageAudioVideo,
|
||||||
BubbleIntegration,
|
BubbleIntegration,
|
||||||
BubbleLocation,
|
BubbleLocation,
|
||||||
BubbleMailHead,
|
BubbleMailHead,
|
||||||
BubbleText,
|
BubbleText,
|
||||||
BubbleVideo,
|
|
||||||
BubbleContact,
|
BubbleContact,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Spinner,
|
Spinner,
|
||||||
@@ -200,10 +195,18 @@ export default {
|
|||||||
hasImageError: false,
|
hasImageError: false,
|
||||||
contextMenuPosition: {},
|
contextMenuPosition: {},
|
||||||
showBackgroundHighlight: false,
|
showBackgroundHighlight: false,
|
||||||
hasStoryError: false,
|
hasImgStoryError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
attachments() {
|
||||||
|
// Here it is used to get sender and created_at for each attachment
|
||||||
|
return this.data?.attachments.map(attachment => ({
|
||||||
|
...attachment,
|
||||||
|
sender: this.data.sender || {},
|
||||||
|
created_at: this.data.created_at || '',
|
||||||
|
}));
|
||||||
|
},
|
||||||
shouldRenderMessage() {
|
shouldRenderMessage() {
|
||||||
return (
|
return (
|
||||||
this.hasAttachments ||
|
this.hasAttachments ||
|
||||||
@@ -429,12 +432,12 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
data() {
|
data() {
|
||||||
this.hasImageError = false;
|
this.hasImageError = false;
|
||||||
this.hasStoryError = false;
|
this.hasImgStoryError = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.hasImageError = false;
|
this.hasImageError = false;
|
||||||
this.hasStoryError = false;
|
this.hasImgStoryError = false;
|
||||||
bus.$on(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL, this.closeContextMenu);
|
bus.$on(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL, this.closeContextMenu);
|
||||||
this.setupHighlightTimer();
|
this.setupHighlightTimer();
|
||||||
},
|
},
|
||||||
@@ -443,6 +446,9 @@ export default {
|
|||||||
clearTimeout(this.higlightTimeout);
|
clearTimeout(this.higlightTimeout);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isAttachmentImageVideoAudio(fileType) {
|
||||||
|
return ['image', 'audio', 'video'].includes(fileType);
|
||||||
|
},
|
||||||
hasMediaAttachment(type) {
|
hasMediaAttachment(type) {
|
||||||
if (this.hasAttachments && this.data.attachments.length > 0) {
|
if (this.hasAttachments && this.data.attachments.length > 0) {
|
||||||
const { attachments = [{}] } = this.data;
|
const { attachments = [{}] } = this.data;
|
||||||
@@ -464,7 +470,7 @@ export default {
|
|||||||
this.hasImageError = true;
|
this.hasImageError = true;
|
||||||
},
|
},
|
||||||
onStoryLoadError() {
|
onStoryLoadError() {
|
||||||
this.hasStoryError = true;
|
this.hasImgStoryError = true;
|
||||||
},
|
},
|
||||||
openContextMenu(e) {
|
openContextMenu(e) {
|
||||||
const shouldSkipContextMenu =
|
const shouldSkipContextMenu =
|
||||||
@@ -514,7 +520,7 @@ export default {
|
|||||||
|
|
||||||
.image,
|
.image,
|
||||||
.video {
|
.video {
|
||||||
max-width: 32rem;
|
max-width: 20rem;
|
||||||
padding: var(--space-micro);
|
padding: var(--space-micro);
|
||||||
|
|
||||||
> img,
|
> img,
|
||||||
@@ -528,13 +534,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.video {
|
.video {
|
||||||
height: 18rem;
|
height: 11.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-image.is-text > .message-text__wrap,
|
&.is-image.is-text > .message-text__wrap,
|
||||||
&.is-video.is-text > .message-text__wrap {
|
&.is-video.is-text > .message-text__wrap {
|
||||||
max-width: 32rem;
|
max-width: 20rem;
|
||||||
padding: var(--space-small) var(--space-normal);
|
padding: var(--space-small) var(--space-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
v-for="message in getReadMessages"
|
v-for="message in getReadMessages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
class="message--read ph-no-capture"
|
class="message--read ph-no-capture"
|
||||||
|
data-clarity-mask="True"
|
||||||
:data="message"
|
:data="message"
|
||||||
:is-a-tweet="isATweet"
|
:is-a-tweet="isATweet"
|
||||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
v-for="message in getUnReadMessages"
|
v-for="message in getUnReadMessages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
class="message--unread ph-no-capture"
|
class="message--unread ph-no-capture"
|
||||||
|
data-clarity-mask="True"
|
||||||
:data="message"
|
:data="message"
|
||||||
:is-a-tweet="isATweet"
|
:is-a-tweet="isATweet"
|
||||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||||
@@ -277,6 +279,7 @@ export default {
|
|||||||
if (newChat.id === oldChat.id) {
|
if (newChat.id === oldChat.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.fetchAllAttachmentsFromCurrentChat();
|
||||||
this.selectedTweetId = null;
|
this.selectedTweetId = null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -288,6 +291,7 @@ export default {
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.addScrollListener();
|
this.addScrollListener();
|
||||||
|
this.fetchAllAttachmentsFromCurrentChat();
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@@ -296,6 +300,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
fetchAllAttachmentsFromCurrentChat() {
|
||||||
|
this.$store.dispatch('fetchAllAttachments', this.currentChat.id);
|
||||||
|
},
|
||||||
removeBusListeners() {
|
removeBusListeners() {
|
||||||
bus.$off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
|
bus.$off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
|
||||||
bus.$off(BUS_EVENTS.SET_TWEET_REPLY, this.setSelectedTweet);
|
bus.$off(BUS_EVENTS.SET_TWEET_REPLY, this.setSelectedTweet);
|
||||||
@@ -423,22 +430,22 @@ export default {
|
|||||||
.modal-mask {
|
.modal-mask {
|
||||||
&::v-deep {
|
&::v-deep {
|
||||||
.ProseMirror-woot-style {
|
.ProseMirror-woot-style {
|
||||||
max-height: 40rem;
|
max-height: 25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box {
|
.reply-box {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
max-width: 120rem;
|
max-width: 75rem;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box .reply-box__top {
|
.reply-box .reply-box__top {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 44rem;
|
min-height: 27.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box__top .input {
|
.reply-box__top .input {
|
||||||
min-height: 44rem;
|
min-height: 27.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-dialog {
|
.emoji-dialog {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user