Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d170fc727 | ||
|
|
8d65e7db95 | ||
|
|
91bccd3c6d | ||
|
|
ef6c695bcb | ||
|
|
1273ea64d3 | ||
|
|
2a4fb7b056 | ||
|
|
a8ac048716 | ||
|
|
fabc6c87af | ||
|
|
f76ba889dd | ||
|
|
97ad512c21 | ||
|
|
dfdb68e1c3 | ||
|
|
c772c0af03 | ||
|
|
cda65ea339 | ||
|
|
d6237dfc59 | ||
|
|
629c73d3ce | ||
|
|
bbf01037c6 | ||
|
|
7f26b34b15 | ||
|
|
bbd9968d4b | ||
|
|
c119c6577b | ||
|
|
e8cf59c661 | ||
|
|
b05f843790 | ||
|
|
0aad99ff0b | ||
|
|
0740d4762f | ||
|
|
e5bc372a29 | ||
|
|
74f8a35cfc | ||
|
|
1f158aa193 | ||
|
|
7479b5db43 | ||
|
|
4d5e7b4adf | ||
|
|
55b001435b | ||
|
|
fc1fa579e9 | ||
|
|
c7a4550477 | ||
|
|
fd8e8c7c87 | ||
|
|
2d3212575d | ||
|
|
e2688d5176 | ||
|
|
eea92bd0ac | ||
|
|
1261cc7783 | ||
|
|
a790dc640f | ||
|
|
67f4f69e8a | ||
|
|
6340eb3ae2 | ||
|
|
708d2688ff | ||
|
|
91f8b4a650 | ||
|
|
330bf87f08 | ||
|
|
bc884ae184 | ||
|
|
f7c8358549 | ||
|
|
d36e1b52f6 | ||
|
|
813cdd8aec | ||
|
|
b7e725a7cd | ||
|
|
9674e0bff4 | ||
|
|
4817f4a28c | ||
|
|
04c62417fa | ||
|
|
30e5edf6dc | ||
|
|
e0afb84502 | ||
|
|
fbefb78817 | ||
|
|
f00e1dcdc8 | ||
|
|
60547b9fb3 | ||
|
|
fc23a10a99 | ||
|
|
44a3977f5c | ||
|
|
a2316d4d09 | ||
|
|
c32d391261 | ||
|
|
c4e2a84f65 | ||
|
|
e61ba95cf7 | ||
|
|
77473dc2aa | ||
|
|
eb5185974a | ||
|
|
919261d843 | ||
|
|
79a847aeab | ||
|
|
301b1829bc | ||
|
|
6645d7f80b | ||
|
|
baf1a6e8da | ||
|
|
1e955455f0 | ||
|
|
fdbad7c5d8 | ||
|
|
2023d5d42b | ||
|
|
6186d14593 | ||
|
|
30f4c08143 | ||
|
|
272c481464 | ||
|
|
3465ebefd1 | ||
|
|
b9836309fb | ||
|
|
6c46891ee1 | ||
|
|
67e41092e6 | ||
|
|
1d269f4e8d | ||
|
|
37289fbd63 | ||
|
|
a30a8867a3 | ||
|
|
5210d201b6 | ||
|
|
3eb5b3a0c1 | ||
|
|
3fa7c605c3 | ||
|
|
7b1fdc5fcd | ||
|
|
607fc25723 | ||
|
|
83b0bb4062 | ||
|
|
33e0bd434b | ||
|
|
002463aede | ||
|
|
f1613f4a0d | ||
|
|
1aebdc63d5 | ||
|
|
8d6f9cad05 | ||
|
|
03a46d0cfe | ||
|
|
067411b5b6 | ||
|
|
cf56b9560d | ||
|
|
a9c304f1ef | ||
|
|
a287c86bc4 | ||
|
|
0b31e14132 | ||
|
|
1f4703d25d | ||
|
|
b3264a0d7b | ||
|
|
199642d3bd | ||
|
|
6325acd183 | ||
|
|
8cf135f2db | ||
|
|
6efa994096 | ||
|
|
2168f823a5 | ||
|
|
1fd4127709 | ||
|
|
04d347f0ee | ||
|
|
d4b3ba4baa | ||
|
|
1d3ed016be | ||
|
|
95fb6893b4 | ||
|
|
a2b025b548 | ||
|
|
90e678743b | ||
|
|
9290893f9d | ||
|
|
f7f7a6b9c7 | ||
|
|
cc180b77ce | ||
|
|
18bf1a9e62 | ||
|
|
7cf19e0b52 | ||
|
|
655c585358 | ||
|
|
fc6a8c2601 | ||
|
|
63789e5916 | ||
|
|
722f540b03 | ||
|
|
59d4eaeca7 | ||
|
|
b00e13f9db | ||
|
|
f875a09fb7 | ||
|
|
f02d422b6a | ||
|
|
91ace96acd |
@@ -12,7 +12,8 @@ defaults: &defaults
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
# documented at https://circleci.com/docs/2.0/circleci-images/
|
||||
- image: circleci/postgres:9.4
|
||||
- image: circleci/postgres:alpine
|
||||
- image: circleci/redis:alpine
|
||||
environment:
|
||||
- CC_TEST_REPORTER_ID: b1b5c4447bf93f6f0b06a64756e35afd0810ea83649f03971cbf303b4449456f
|
||||
|
||||
@@ -94,24 +95,24 @@ jobs:
|
||||
command: yarn run eslint
|
||||
|
||||
# Run rails tests
|
||||
- run:
|
||||
- run:
|
||||
name: Run backend tests
|
||||
command: |
|
||||
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
|
||||
./tmp/cc-test-reporter format-coverage -t simplecov -o tmp/codeclimate.backend.json coverage/backend/.resultset.json
|
||||
- persist_to_workspace:
|
||||
root: tmp
|
||||
paths:
|
||||
paths:
|
||||
- codeclimate.backend.json
|
||||
|
||||
- run:
|
||||
- run:
|
||||
name: Run frontend tests
|
||||
command: |
|
||||
yarn test:coverage
|
||||
./tmp/cc-test-reporter format-coverage -t lcov -o tmp/codeclimate.frontend.json buildreports/lcov.info
|
||||
- persist_to_workspace:
|
||||
root: tmp
|
||||
paths:
|
||||
paths:
|
||||
- codeclimate.frontend.json
|
||||
|
||||
# collect reports
|
||||
@@ -125,4 +126,4 @@ jobs:
|
||||
name: Upload coverage results to Code Climate
|
||||
command: |
|
||||
./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json -p 2 -o tmp/codeclimate.total.json
|
||||
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
|
||||
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ plugins:
|
||||
scss-lint:
|
||||
enabled: true
|
||||
brakeman:
|
||||
enabled: true
|
||||
enabled: false
|
||||
checks:
|
||||
similar-code:
|
||||
enabled: false
|
||||
|
||||
+32
-2
@@ -1,5 +1,19 @@
|
||||
REDIS_URL=redis://redis:6379
|
||||
SECRET_KEY_BASE=
|
||||
# Force all access to the app over SSL, default is set to false
|
||||
FORCE_SSL=
|
||||
|
||||
# This lets you control new sign ups on your chatwoot installation
|
||||
# true : default option, allows sign ups
|
||||
# false : disables all the end points related to sign ups
|
||||
# api_only: disables the UI for signup, but you can create sign ups via the account apis
|
||||
ENABLE_ACCOUNT_SIGNUP=
|
||||
|
||||
#redis config
|
||||
REDIS_URL=redis://redis:6379
|
||||
# If you are using docker-compose, set this variable's value to be any string,
|
||||
# which will be the password for the redis service running inside the docker-compose
|
||||
# to make it secure
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# Postgres Database config variables
|
||||
POSTGRES_HOST=postgres
|
||||
@@ -13,10 +27,18 @@ FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
|
||||
#twitter app
|
||||
TWITTER_APP_ID=
|
||||
TWITTER_CONSUMER_KEY=
|
||||
TWITTER_CONSUMER_SECRET=
|
||||
TWITTER_ENVIRONMENT=
|
||||
|
||||
#mail
|
||||
MAILER_SENDER_EMAIL=accounts@chatwoot.com
|
||||
SMTP_PORT=1025
|
||||
SMTP_DOMAIN=chatwoot.com
|
||||
# if you are running docker-compose, set SMTP_ADDRESS value as "mailhog",
|
||||
# else set the value as "localhost"
|
||||
SMTP_ADDRESS=mailhog
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
@@ -36,6 +58,14 @@ AWS_REGION=
|
||||
#sentry
|
||||
SENTRY_DSN=
|
||||
|
||||
#Log settings
|
||||
LOG_LEVEL=
|
||||
LOG_SIZE=
|
||||
|
||||
# Credentials to access sidekiq dashboard in production
|
||||
SIDEKIQ_AUTH_USERNAME=
|
||||
SIDEKIQ_AUTH_PASSWORD=
|
||||
|
||||
#### This environment variables are only required in hosted version which has billing
|
||||
ENABLE_BILLING=
|
||||
|
||||
@@ -43,4 +73,4 @@ ENABLE_BILLING=
|
||||
CHARGEBEE_API_KEY=
|
||||
CHARGEBEE_SITE=
|
||||
CHARGEBEE_WEBHOOK_USERNAME=
|
||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
||||
|
||||
+4
-2
@@ -24,10 +24,12 @@ module.exports = {
|
||||
'multiline': {
|
||||
'max': 1,
|
||||
'allowFirstLine': false
|
||||
}
|
||||
},
|
||||
}],
|
||||
'vue/html-self-closing': 'off',
|
||||
"vue/no-v-html": 'off'
|
||||
"vue/no-v-html": 'off',
|
||||
'import/extensions': ['never']
|
||||
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
||||
+7
-1
@@ -37,6 +37,10 @@ public/packs*
|
||||
*.swo
|
||||
*.un~
|
||||
.jest-cache
|
||||
|
||||
#VS Code files
|
||||
.vscode
|
||||
|
||||
# ignore jetbrains IDE files
|
||||
.idea
|
||||
|
||||
@@ -48,4 +52,6 @@ coverage
|
||||
|
||||
# ignore packages
|
||||
node_modules
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
|
||||
*.dump
|
||||
|
||||
+11
-2
@@ -4,21 +4,28 @@ require:
|
||||
- rubocop-rspec
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
Metrics/LineLength:
|
||||
Layout/LineLength:
|
||||
Max: 150
|
||||
Metrics/ClassLength:
|
||||
Max: 125
|
||||
RSpec/ExampleLength:
|
||||
Max: 15
|
||||
Documentation:
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
Style/GlobalVars:
|
||||
Exclude:
|
||||
- 'config/initializers/redis.rb'
|
||||
- 'lib/redis/alfred.rb'
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
- '**/routes.rb'
|
||||
- 'config/environments/*'
|
||||
- db/schema.rb
|
||||
Rails/ApplicationController:
|
||||
Exclude:
|
||||
- 'app/controllers/api/v1/widget/messages_controller.rb'
|
||||
@@ -32,6 +39,8 @@ Style/ClassAndModuleChildren:
|
||||
RSpec/NestedGroups:
|
||||
Enabled: true
|
||||
Max: 4
|
||||
RSpec/MessageSpies:
|
||||
Enabled: false
|
||||
AllCops:
|
||||
Exclude:
|
||||
- db/*
|
||||
|
||||
+274
@@ -1,7 +1,281 @@
|
||||
# Default application configuration that all configurations inherit from.
|
||||
|
||||
scss_files: '**/*.scss'
|
||||
plugin_directories: ['.scss-linters']
|
||||
|
||||
# List of gem names to load custom linters from (make sure they are already
|
||||
# installed)
|
||||
plugin_gems: []
|
||||
|
||||
# Default severity of all linters.
|
||||
severity: warning
|
||||
|
||||
linters:
|
||||
BangFormat:
|
||||
enabled: true
|
||||
space_before_bang: true
|
||||
space_after_bang: false
|
||||
|
||||
BemDepth:
|
||||
enabled: false
|
||||
max_elements: 1
|
||||
|
||||
BorderZero:
|
||||
enabled: true
|
||||
convention: zero # or `none`
|
||||
|
||||
ChainedClasses:
|
||||
enabled: false
|
||||
|
||||
ColorKeyword:
|
||||
enabled: true
|
||||
|
||||
ColorVariable:
|
||||
enabled: true
|
||||
|
||||
Comment:
|
||||
enabled: true
|
||||
style: silent
|
||||
|
||||
DebugStatement:
|
||||
enabled: true
|
||||
|
||||
DeclarationOrder:
|
||||
enabled: true
|
||||
|
||||
DisableLinterReason:
|
||||
enabled: false
|
||||
|
||||
DuplicateProperty:
|
||||
enabled: true
|
||||
|
||||
ElsePlacement:
|
||||
enabled: true
|
||||
style: same_line # or 'new_line'
|
||||
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
ignore_single_line_blocks: true
|
||||
|
||||
EmptyRule:
|
||||
enabled: true
|
||||
|
||||
ExtendDirective:
|
||||
enabled: false
|
||||
|
||||
FinalNewline:
|
||||
enabled: true
|
||||
present: true
|
||||
|
||||
HexLength:
|
||||
enabled: true
|
||||
style: short # or 'long'
|
||||
|
||||
HexNotation:
|
||||
enabled: true
|
||||
style: lowercase # or 'uppercase'
|
||||
|
||||
HexValidation:
|
||||
enabled: true
|
||||
|
||||
IdSelector:
|
||||
enabled: true
|
||||
|
||||
ImportantRule:
|
||||
enabled: true
|
||||
|
||||
ImportPath:
|
||||
enabled: true
|
||||
leading_underscore: false
|
||||
filename_extension: false
|
||||
|
||||
Indentation:
|
||||
enabled: true
|
||||
allow_non_nested_indentation: false
|
||||
character: space # or 'tab'
|
||||
width: 2
|
||||
|
||||
LeadingZero:
|
||||
enabled: false
|
||||
|
||||
MergeableSelector:
|
||||
enabled: true
|
||||
force_nesting: true
|
||||
|
||||
NameFormat:
|
||||
enabled: true
|
||||
allow_leading_underscore: true
|
||||
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
|
||||
|
||||
NestingDepth:
|
||||
enabled: true
|
||||
max_depth: 6
|
||||
ignore_parent_selectors: false
|
||||
|
||||
PlaceholderInExtend:
|
||||
enabled: true
|
||||
|
||||
PrivateNamingConvention:
|
||||
enabled: false
|
||||
prefix: _
|
||||
|
||||
PropertyCount:
|
||||
enabled: false
|
||||
include_nested: false
|
||||
max_properties: 10
|
||||
|
||||
PropertySortOrder:
|
||||
enabled: true
|
||||
ignore_unspecified: false
|
||||
min_properties: 2
|
||||
separate_groups: false
|
||||
|
||||
PropertySpelling:
|
||||
enabled: true
|
||||
extra_properties: []
|
||||
disabled_properties: []
|
||||
|
||||
PropertyUnits:
|
||||
enabled: true
|
||||
global: [
|
||||
'ch',
|
||||
'em',
|
||||
'ex',
|
||||
'rem', # Font-relative lengths
|
||||
'cm',
|
||||
'in',
|
||||
'mm',
|
||||
'pc',
|
||||
'pt',
|
||||
'px',
|
||||
'q', # Absolute lengths
|
||||
'vh',
|
||||
'vw',
|
||||
'vmin',
|
||||
'vmax', # Viewport-percentage lengths
|
||||
'fr', # Grid fractional lengths
|
||||
'deg',
|
||||
'grad',
|
||||
'rad',
|
||||
'turn', # Angle
|
||||
'ms',
|
||||
's', # Duration
|
||||
'Hz',
|
||||
'kHz', # Frequency
|
||||
'dpi',
|
||||
'dpcm',
|
||||
'dppx', # Resolution
|
||||
'%',
|
||||
] # Other
|
||||
properties: {}
|
||||
|
||||
PseudoElement:
|
||||
enabled: true
|
||||
|
||||
QualifyingElement:
|
||||
enabled: true
|
||||
allow_element_with_attribute: false
|
||||
allow_element_with_class: false
|
||||
allow_element_with_id: false
|
||||
|
||||
SelectorDepth:
|
||||
enabled: true
|
||||
max_depth: 5
|
||||
|
||||
SelectorFormat:
|
||||
enabled: false
|
||||
|
||||
Shorthand:
|
||||
enabled: true
|
||||
allowed_shorthands: [1, 2, 3, 4]
|
||||
|
||||
SingleLinePerProperty:
|
||||
enabled: true
|
||||
allow_single_line_rule_sets: true
|
||||
|
||||
SingleLinePerSelector:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterComma:
|
||||
enabled: true
|
||||
style: one_space # or 'no_space', or 'at_least_one_space'
|
||||
|
||||
SpaceAfterComment:
|
||||
enabled: false
|
||||
style: one_space # or 'no_space', or 'at_least_one_space'
|
||||
allow_empty_comments: true
|
||||
|
||||
SpaceAfterPropertyColon:
|
||||
enabled: true
|
||||
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
|
||||
|
||||
SpaceAfterPropertyName:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterVariableColon:
|
||||
enabled: false
|
||||
style: one_space # or 'no_space', 'at_least_one_space' or 'one_space_or_newline'
|
||||
|
||||
SpaceAfterVariableName:
|
||||
enabled: true
|
||||
|
||||
SpaceAroundOperator:
|
||||
enabled: true
|
||||
style: one_space # or 'at_least_one_space', or 'no_space'
|
||||
|
||||
SpaceBeforeBrace:
|
||||
enabled: true
|
||||
style: space # or 'new_line'
|
||||
allow_single_line_padding: false
|
||||
|
||||
SpaceBetweenParens:
|
||||
enabled: true
|
||||
spaces: 0
|
||||
|
||||
StringQuotes:
|
||||
enabled: true
|
||||
style: single_quotes # or double_quotes
|
||||
|
||||
TrailingSemicolon:
|
||||
enabled: true
|
||||
|
||||
TrailingWhitespace:
|
||||
enabled: true
|
||||
|
||||
TrailingZero:
|
||||
enabled: false
|
||||
|
||||
TransitionAll:
|
||||
enabled: false
|
||||
|
||||
UnnecessaryMantissa:
|
||||
enabled: false
|
||||
|
||||
UnnecessaryParentReference:
|
||||
enabled: true
|
||||
|
||||
UrlFormat:
|
||||
enabled: true
|
||||
|
||||
UrlQuotes:
|
||||
enabled: true
|
||||
|
||||
VariableForProperty:
|
||||
enabled: false
|
||||
properties: []
|
||||
|
||||
VendorPrefix:
|
||||
enabled: true
|
||||
identifier_list: base
|
||||
additional_identifiers: []
|
||||
excluded_identifiers: []
|
||||
|
||||
ZeroUnit:
|
||||
enabled: true
|
||||
|
||||
Compass::*:
|
||||
enabled: false
|
||||
|
||||
exclude:
|
||||
- 'app/javascript/widget/assets/scss/_reset.scss'
|
||||
- 'app/javascript/widget/assets/scss/sdk.css'
|
||||
|
||||
@@ -16,6 +16,7 @@ gem 'hashie'
|
||||
gem 'jbuilder'
|
||||
gem 'kaminari'
|
||||
gem 'responders'
|
||||
gem 'rest-client'
|
||||
gem 'time_diff'
|
||||
gem 'tzinfo-data'
|
||||
gem 'valid_email2'
|
||||
@@ -23,6 +24,9 @@ gem 'valid_email2'
|
||||
gem 'uglifier'
|
||||
|
||||
##-- for active storage --##
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'azure-storage', require: false
|
||||
gem 'google-cloud-storage', require: false
|
||||
gem 'mini_magick'
|
||||
|
||||
##-- gems for database --#
|
||||
@@ -57,7 +61,9 @@ gem 'chargebee'
|
||||
##--- gems for channels ---##
|
||||
gem 'facebook-messenger'
|
||||
gem 'telegram-bot-ruby'
|
||||
gem 'twitter'
|
||||
# twitty will handle subscription of twitter account events
|
||||
gem 'twitty', git: 'https://github.com/chatwoot/twitty'
|
||||
|
||||
# facebook client
|
||||
gem 'koala'
|
||||
# Random name generator
|
||||
@@ -66,21 +72,27 @@ gem 'haikunator'
|
||||
##--- gems for debugging and error reporting ---##
|
||||
# static analysis
|
||||
gem 'brakeman'
|
||||
gem 'scout_apm'
|
||||
gem 'sentry-raven'
|
||||
|
||||
##-- TODO: move these gems to appropriate groups --##
|
||||
# remove this gem in favor of active storage - github #158
|
||||
gem 'carrierwave-aws'
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq'
|
||||
|
||||
##-- used for single column multiple binary flags in notification settings/feature flagging --##
|
||||
gem 'flag_shih_tzu'
|
||||
|
||||
group :development do
|
||||
gem 'annotate'
|
||||
gem 'bullet'
|
||||
gem 'letter_opener'
|
||||
gem 'web-console'
|
||||
|
||||
# used in swagger build
|
||||
gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: 'e32deb0'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
# locking until https://github.com/codeclimate/test-reporter/issues/418 is resolved
|
||||
gem 'action-cable-testing'
|
||||
gem 'bundle-audit', require: false
|
||||
gem 'byebug', platform: :mri
|
||||
@@ -94,9 +106,10 @@ group :development, :test do
|
||||
gem 'rubocop-performance', require: false
|
||||
gem 'rubocop-rails', require: false
|
||||
gem 'rubocop-rspec', require: false
|
||||
gem 'scss_lint', require: false
|
||||
gem 'seed_dump'
|
||||
gem 'shoulda-matchers'
|
||||
gem 'simplecov', require: false
|
||||
gem 'simplecov', '0.17.1', require: false
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen'
|
||||
end
|
||||
|
||||
+150
-97
@@ -1,3 +1,18 @@
|
||||
GIT
|
||||
remote: https://github.com/chatwoot/twitty
|
||||
revision: c1edd557401d1e8a197b19e738f82e39507a8e2d
|
||||
specs:
|
||||
twitty (0.1.0)
|
||||
oauth
|
||||
|
||||
GIT
|
||||
remote: https://github.com/tzmfreedom/json_refs
|
||||
revision: e32deb073ce9aef39bdd63556bffd7fe7c2a803d
|
||||
ref: e32deb0
|
||||
specs:
|
||||
json_refs (0.1.2)
|
||||
hana
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@@ -66,15 +81,15 @@ GEM
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.0)
|
||||
attr_extras (6.2.1)
|
||||
attr_extras (6.2.3)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.259.0)
|
||||
aws-sdk-core (3.86.0)
|
||||
aws-partitions (1.269.0)
|
||||
aws-sdk-core (3.89.1)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.27.0)
|
||||
aws-sdk-kms (1.28.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.60.1)
|
||||
@@ -87,15 +102,23 @@ GEM
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
azure-core (0.1.15)
|
||||
faraday (~> 0.9)
|
||||
faraday_middleware (~> 0.10)
|
||||
nokogiri (~> 1.6)
|
||||
azure-storage (0.15.0.preview)
|
||||
azure-core (~> 0.1)
|
||||
faraday (~> 0.9)
|
||||
faraday_middleware (~> 0.10)
|
||||
nokogiri (~> 1.6, >= 1.6.8)
|
||||
bcrypt (3.1.13)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.4.5)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.7.2)
|
||||
browser (2.7.1)
|
||||
buftok (0.2.0)
|
||||
browser (3.0.3)
|
||||
builder (3.2.4)
|
||||
bullet (6.0.2)
|
||||
bullet (6.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundle-audit (0.1.0)
|
||||
@@ -103,18 +126,8 @@ GEM
|
||||
bundler-audit (0.6.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 0.18)
|
||||
byebug (11.0.1)
|
||||
carrierwave (2.0.2)
|
||||
activemodel (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
addressable (~> 2.6)
|
||||
image_processing (~> 1.1)
|
||||
mimemagic (>= 0.3.0)
|
||||
mini_mime (>= 0.1.3)
|
||||
carrierwave-aws (1.4.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
carrierwave (>= 0.7, < 2.1)
|
||||
chargebee (2.7.1)
|
||||
byebug (11.1.1)
|
||||
chargebee (2.7.3)
|
||||
json_pure (~> 2.1)
|
||||
rest-client (>= 1.8, < 3.0)
|
||||
coderay (1.1.2)
|
||||
@@ -122,7 +135,9 @@ GEM
|
||||
descendants_tracker (~> 0.0.1)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
crass (1.0.5)
|
||||
crass (1.0.6)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.7.1)
|
||||
@@ -136,6 +151,7 @@ GEM
|
||||
devise (> 3.5.2, < 5)
|
||||
rails (>= 4.2.0, < 6.1)
|
||||
diff-lcs (1.3)
|
||||
digest-crc (0.4.1)
|
||||
docile (1.3.2)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
@@ -154,35 +170,58 @@ GEM
|
||||
factory_bot_rails (5.1.1)
|
||||
factory_bot (~> 5.1.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.9.0)
|
||||
i18n (>= 1.6, < 1.8)
|
||||
faraday (0.17.1)
|
||||
faker (2.10.1)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (0.17.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.11.3)
|
||||
foreman (0.86.0)
|
||||
faraday_middleware (0.14.0)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
ffi (1.12.2)
|
||||
flag_shih_tzu (0.3.23)
|
||||
foreman (0.87.0)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.36.4)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.25.1)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.10.0)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.12)
|
||||
haikunator (1.1.0)
|
||||
hashie (4.0.0)
|
||||
http (3.3.0)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 2.0)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
hana (1.3.5)
|
||||
hashie (4.1.0)
|
||||
http-accept (1.7.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.1.1)
|
||||
http_parser.rb (0.6.0)
|
||||
httparty (0.17.3)
|
||||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.7.0)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
image_processing (1.10.0)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.13, < 3)
|
||||
inflecto (0.0.2)
|
||||
jaro_winkler (1.5.4)
|
||||
jbuilder (2.9.1)
|
||||
@@ -191,18 +230,18 @@ GEM
|
||||
json (2.3.0)
|
||||
json_pure (2.2.0)
|
||||
jwt (2.2.1)
|
||||
kaminari (1.1.1)
|
||||
kaminari (1.2.0)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.1.1)
|
||||
kaminari-activerecord (= 1.1.1)
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-actionview (1.1.1)
|
||||
kaminari-actionview (= 1.2.0)
|
||||
kaminari-activerecord (= 1.2.0)
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-actionview (1.2.0)
|
||||
actionview
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-activerecord (1.1.1)
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-activerecord (1.2.0)
|
||||
activerecord
|
||||
kaminari-core (= 1.1.1)
|
||||
kaminari-core (1.1.1)
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-core (1.2.0)
|
||||
koala (3.0.0)
|
||||
addressable
|
||||
faraday
|
||||
@@ -221,48 +260,49 @@ GEM
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
memoist (0.16.2)
|
||||
method_source (0.9.2)
|
||||
mime-types (3.3)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.1009)
|
||||
mimemagic (0.3.3)
|
||||
mini_magick (4.9.5)
|
||||
mimemagic (0.3.4)
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.13.0)
|
||||
minitest (5.14.0)
|
||||
mock_redis (0.22.0)
|
||||
msgpack (1.3.1)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
naught (1.1.0)
|
||||
netrc (0.11.0)
|
||||
nightfury (1.0.1)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.7)
|
||||
nokogiri (1.10.8)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
oauth (0.5.4)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.0.1)
|
||||
parallel (1.19.1)
|
||||
parser (2.6.5.0)
|
||||
parser (2.7.0.2)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.1.4)
|
||||
pg (1.2.2)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.1)
|
||||
puma (4.3.1)
|
||||
public_suffix (4.0.3)
|
||||
puma (4.3.3)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.0.8)
|
||||
rack-cache (1.10.0)
|
||||
rack (2.1.2)
|
||||
rack-cache (1.11.0)
|
||||
rack (>= 0.4)
|
||||
rack-cors (1.1.0)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (2.0.7)
|
||||
rack-protection (2.0.8.1)
|
||||
rack
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
@@ -297,7 +337,7 @@ GEM
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (4.1.3)
|
||||
redis-namespace (1.7.0)
|
||||
@@ -307,6 +347,10 @@ GEM
|
||||
redis-store (>= 1.6, < 2)
|
||||
redis-store (1.8.1)
|
||||
redis (>= 4, < 5)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
@@ -315,53 +359,65 @@ GEM
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
rspec-core (3.9.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
retriable (3.1.2)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.0.beta3)
|
||||
rspec-rails (4.0.0.beta4)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.8)
|
||||
rspec-expectations (~> 3.8)
|
||||
rspec-mocks (~> 3.8)
|
||||
rspec-support (~> 3.8)
|
||||
rspec-support (3.9.0)
|
||||
rubocop (0.78.0)
|
||||
rspec-core (~> 3.9)
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.2)
|
||||
rubocop (0.79.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-performance (1.5.1)
|
||||
rubocop-performance (1.5.2)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.4.0)
|
||||
rubocop-rails (2.4.2)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
rubocop-rspec (1.37.1)
|
||||
rubocop (>= 0.68.1)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-vips (2.0.16)
|
||||
ffi (~> 1.9)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
scout_apm (2.6.6)
|
||||
parser
|
||||
scss_lint (0.59.0)
|
||||
sass (~> 3.5, >= 3.5.5)
|
||||
seed_dump (3.3.1)
|
||||
activerecord (>= 4)
|
||||
activesupport (>= 4)
|
||||
sentry-raven (2.13.0)
|
||||
faraday (>= 0.7.6, < 1.0)
|
||||
shoulda-matchers (4.1.2)
|
||||
shoulda-matchers (4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
sidekiq (6.0.4)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (>= 2.0.0)
|
||||
redis (>= 4.1.0)
|
||||
simple_oauth (0.3.1)
|
||||
signet (0.12.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.17.1)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
@@ -378,7 +434,7 @@ GEM
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
telegram-bot-ruby (0.11.0)
|
||||
telegram-bot-ruby (0.12.0)
|
||||
faraday
|
||||
inflecto
|
||||
virtus
|
||||
@@ -387,27 +443,17 @@ GEM
|
||||
time_diff (0.3.0)
|
||||
activesupport
|
||||
i18n
|
||||
twitter (6.2.0)
|
||||
addressable (~> 2.3)
|
||||
buftok (~> 0.2.0)
|
||||
equalizer (~> 0.0.11)
|
||||
http (~> 3.0)
|
||||
http-form_data (~> 2.0)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
memoizable (~> 0.4.0)
|
||||
multipart-post (~> 2.0)
|
||||
naught (~> 1.0)
|
||||
simple_oauth (~> 0.3.0)
|
||||
tzinfo (1.2.5)
|
||||
tzinfo (1.2.6)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2019.3)
|
||||
tzinfo (>= 1.0.0)
|
||||
uber (0.1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
unicode-display_width (1.6.1)
|
||||
uniform_notifier (1.13.0)
|
||||
valid_email2 (3.1.3)
|
||||
activemodel (>= 3.2)
|
||||
@@ -442,13 +488,14 @@ DEPENDENCIES
|
||||
acts-as-taggable-on
|
||||
annotate
|
||||
attr_extras
|
||||
aws-sdk-s3
|
||||
azure-storage
|
||||
bootsnap
|
||||
brakeman
|
||||
browser
|
||||
bullet
|
||||
bundle-audit
|
||||
byebug
|
||||
carrierwave-aws
|
||||
chargebee
|
||||
devise
|
||||
devise_token_auth
|
||||
@@ -456,10 +503,13 @@ DEPENDENCIES
|
||||
facebook-messenger
|
||||
factory_bot_rails
|
||||
faker
|
||||
flag_shih_tzu
|
||||
foreman
|
||||
google-cloud-storage
|
||||
haikunator
|
||||
hashie
|
||||
jbuilder
|
||||
json_refs!
|
||||
jwt
|
||||
kaminari
|
||||
koala
|
||||
@@ -478,21 +528,24 @@ DEPENDENCIES
|
||||
redis-namespace
|
||||
redis-rack-cache
|
||||
responders
|
||||
rest-client
|
||||
rspec-rails (~> 4.0.0.beta2)
|
||||
rubocop
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
scout_apm
|
||||
scss_lint
|
||||
seed_dump
|
||||
sentry-raven
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
simplecov
|
||||
simplecov (= 0.17.1)
|
||||
spring
|
||||
spring-watcher-listen
|
||||
telegram-bot-ruby
|
||||
time_diff
|
||||
twitter
|
||||
twitty!
|
||||
tzinfo-data
|
||||
uglifier
|
||||
valid_email2
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
backend: bin/rails s -p 3000
|
||||
frontend: bin/webpack-dev-server
|
||||
worker: bundle exec sidekiq
|
||||
worker: bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
@@ -37,6 +37,11 @@ Detailed documentation is available at [www.chatwoot.com/docs](https://www.chatw
|
||||
|
||||
You can find the quick setup docs [here](https://www.chatwoot.com/docs/quick-setup).
|
||||
|
||||
## Branching model
|
||||
|
||||
We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`.
|
||||
If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`.
|
||||
|
||||
## Heroku one-click deploy
|
||||
|
||||
Deploying chatwoot to heroku, it's a breeze. It's as simple as clicking this button.
|
||||
@@ -47,16 +52,10 @@ Follow this [link](https://www.chatwoot.com/docs/environment-variables) to under
|
||||
|
||||
## Docker
|
||||
|
||||
You can use our official Docker image from [https://hub.docker.com/r/chatwoot/chatwoot](https://hub.docker.com/r/chatwoot/chatwoot)
|
||||
|
||||
```bash
|
||||
docker pull chatwoot/chatwoot
|
||||
```
|
||||
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using `docker-compose`.
|
||||
|
||||
Follow our [environment variables](https://www.chatwoot.com/docs/environment-variables/) guide to setup environment for Docker.
|
||||
|
||||
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using docker composer.
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contributors):
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = '';
|
||||
@@ -2,129 +2,141 @@ require 'open-uri'
|
||||
|
||||
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
# Assumptions
|
||||
# 1. Incase of an outgoing message which is echo, fb_id will NOT be nil,
|
||||
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
|
||||
# based on this we are showing "not sent from chatwoot" message in frontend
|
||||
# Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
|
||||
module Messages
|
||||
class MessageBuilder
|
||||
attr_reader :response
|
||||
class Messages::MessageBuilder
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, inbox, outgoing_echo = false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (outgoing_echo ? :outgoing : :incoming)
|
||||
def initialize(response, inbox, outgoing_echo = false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (outgoing_echo ? :outgoing : :incoming)
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
end
|
||||
private
|
||||
|
||||
private
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
avatar_resource = LocalResource.new(contact_params[:remote_avatar_url])
|
||||
@contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
|
||||
@contact = Contact.create!(contact_params)
|
||||
ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.new(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
@message.build_attachment(attachment_params(attachment))
|
||||
end
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def build_attachment; end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || Conversation.create!(conversation_params)
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = { file_type: file_type, account_id: @message.account_id }
|
||||
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
fb_id: response.identifier
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue Exception => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || nil
|
||||
}
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.build_attachment(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = { file_type: file_type, account_id: @message.account_id }
|
||||
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue Exception => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ class Messages::Outgoing::NormalBuilder
|
||||
content: @content,
|
||||
private: @private,
|
||||
user_id: @user.id,
|
||||
fb_id: @fb_id
|
||||
source_id: @fb_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
class RoomChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from params[:pubsub_token]
|
||||
::OnlineStatusTracker.add_subscription(params[:pubsub_token])
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
::OnlineStatusTracker.remove_subscription(params[:pubsub_token])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
class Api::BaseController < ApplicationController
|
||||
respond_to :json
|
||||
before_action :authenticate_user!
|
||||
unless Rails.env.development?
|
||||
rescue_from StandardError do |exception|
|
||||
Raven.capture_exception(exception)
|
||||
render json: { error: '500 error', message: exception.message }.to_json, status: 500
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
|
||||
end
|
||||
|
||||
def check_billing_enabled
|
||||
raise ActionController::RoutingError, 'Not Found' unless ENV['BILLING_ENABLED']
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
class Api::V1::Account::WebhooksController < Api::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_webhook, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@webhooks = current_account.webhooks
|
||||
end
|
||||
|
||||
def create
|
||||
@webhook = current_account.webhooks.new(webhook_params)
|
||||
@webhook.save!
|
||||
end
|
||||
|
||||
def update
|
||||
@webhook.update!(webhook_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@webhook.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def webhook_params
|
||||
params.require(:webhook).permit(:inbox_id, :url)
|
||||
end
|
||||
|
||||
def fetch_webhook
|
||||
@webhook = current_account.webhooks.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Webhook)
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
skip_before_action :verify_authenticity_token, only: [:create]
|
||||
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
||||
only: [:create], raise: false
|
||||
before_action :check_signup_enabled
|
||||
|
||||
rescue_from CustomExceptions::Account::InvalidEmail,
|
||||
CustomExceptions::Account::UserExists,
|
||||
@@ -30,4 +31,8 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
def account_params
|
||||
params.permit(:account_name, :email)
|
||||
end
|
||||
|
||||
def check_signup_enabled
|
||||
raise ActionController::RoutingError, 'Not Found' if ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) == 'false'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ class Api::V1::AgentsController < Api::BaseController
|
||||
before_action :build_agent, only: [:create]
|
||||
|
||||
def index
|
||||
render json: agents
|
||||
@agents = agents
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
require 'rest-client'
|
||||
require 'telegram/bot'
|
||||
class Api::V1::CallbacksController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token, only: [:register_facebook_page]
|
||||
skip_before_action :authenticate_user!, only: [:register_facebook_page], raise: false
|
||||
class Api::V1::CallbacksController < Api::BaseController
|
||||
before_action :inbox, only: [:reauthorize_page]
|
||||
|
||||
def register_facebook_page
|
||||
user_access_token = params[:user_access_token]
|
||||
@@ -12,8 +11,9 @@ class Api::V1::CallbacksController < ApplicationController
|
||||
inbox_name = params[:inbox_name]
|
||||
facebook_channel = current_account.facebook_pages.create!(
|
||||
name: page_name, page_id: page_id, user_access_token: user_access_token,
|
||||
page_access_token: page_access_token, remote_avatar_url: set_avatar(page_id)
|
||||
page_access_token: page_access_token
|
||||
)
|
||||
set_avatar(facebook_channel, page_id)
|
||||
inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
render json: inbox
|
||||
end
|
||||
@@ -24,15 +24,13 @@ class Api::V1::CallbacksController < ApplicationController
|
||||
|
||||
# get params[:inbox_id], current_account, params[:omniauth_token]
|
||||
def reauthorize_page
|
||||
if @inbox&.first&.facebook?
|
||||
if @inbox&.facebook?
|
||||
fb_page_id = @inbox.channel.page_id
|
||||
page_details = fb_object.get_connections('me', 'accounts')
|
||||
|
||||
(page_details || []).each do |page_detail|
|
||||
if fb_page_id == page_detail['id'] # found the page which has to be reauthorised
|
||||
update_fb_page(fb_page_id, page_detail['access_token'])
|
||||
head :ok
|
||||
end
|
||||
if (page_detail = (page_details || []).detect { |page| fb_page_id == page['id'] })
|
||||
update_fb_page(fb_page_id, page_detail['access_token'])
|
||||
return head :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,18 +43,13 @@ class Api::V1::CallbacksController < ApplicationController
|
||||
@inbox = current_account.inboxes.find_by(id: params[:inbox_id])
|
||||
end
|
||||
|
||||
def update_fb_page
|
||||
if fb_page(fb_page_id)
|
||||
fb_page.update_attributes!(
|
||||
user_access_token: @user_access_token, page_access_token: access_token
|
||||
)
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
def update_fb_page(fb_page_id, access_token)
|
||||
get_fb_page(fb_page_id)&.update!(
|
||||
user_access_token: @user_access_token, page_access_token: access_token
|
||||
)
|
||||
end
|
||||
|
||||
def fb_page(fb_page_id)
|
||||
def get_fb_page(fb_page_id)
|
||||
current_account.facebook_pages.find_by(page_id: fb_page_id)
|
||||
end
|
||||
|
||||
@@ -67,7 +60,7 @@ class Api::V1::CallbacksController < ApplicationController
|
||||
|
||||
def long_lived_token(omniauth_token)
|
||||
koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET'])
|
||||
long_lived_token = koala.exchange_access_token_info(omniauth_token)['access_token']
|
||||
koala.exchange_access_token_info(omniauth_token)['access_token']
|
||||
end
|
||||
|
||||
def mark_already_existing_facebook_pages(data)
|
||||
@@ -79,7 +72,16 @@ class Api::V1::CallbacksController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def set_avatar(page_id)
|
||||
def set_avatar(facebook_channel, page_id)
|
||||
uri = get_avatar_url(page_id)
|
||||
|
||||
return unless uri
|
||||
|
||||
avatar_resource = LocalResource.new(uri)
|
||||
facebook_channel.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
end
|
||||
|
||||
def get_avatar_url(page_id)
|
||||
begin
|
||||
url = 'http://graph.facebook.com/' << page_id << '/picture?type=large'
|
||||
uri = URI.parse(url)
|
||||
@@ -92,7 +94,6 @@ class Api::V1::CallbacksController < ApplicationController
|
||||
raise
|
||||
end
|
||||
pic_url = response.base_uri.to_s
|
||||
Rails.logger.info(pic_url)
|
||||
rescue StandardError => e
|
||||
pic_url = nil
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
class Api::V1::Contacts::ConversationsController < Api::BaseController
|
||||
def index
|
||||
@conversations = current_account.conversations.includes(
|
||||
:assignee, :contact, :inbox
|
||||
).where(inbox_id: inbox_ids, contact_id: permitted_params[:contact_id])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inbox_ids
|
||||
if current_user.administrator?
|
||||
current_account.inboxes.pluck(:id)
|
||||
elsif current_user.agent?
|
||||
current_user.assigned_inboxes.pluck(:id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:contact_id)
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,18 @@
|
||||
class Api::V1::Conversations::MessagesController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create]
|
||||
before_action :set_conversation, only: [:index, :create]
|
||||
|
||||
def index
|
||||
@messages = message_finder.perform
|
||||
end
|
||||
|
||||
def create
|
||||
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
|
||||
@message = mb.perform
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_finder
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
class Api::V1::ConversationsController < Api::BaseController
|
||||
before_action :set_conversation, except: [:index, :get_messages]
|
||||
|
||||
# TODO: move this to public controller
|
||||
skip_before_action :authenticate_user!, only: [:get_messages]
|
||||
skip_before_action :set_current_user, only: [:get_messages]
|
||||
skip_before_action :check_subscription, only: [:get_messages]
|
||||
skip_around_action :handle_with_exception, only: [:get_messages]
|
||||
before_action :set_conversation, except: [:index]
|
||||
|
||||
def index
|
||||
result = conversation_finder.perform
|
||||
@@ -13,9 +7,7 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def show
|
||||
@messages = messages_finder.perform
|
||||
end
|
||||
def show; end
|
||||
|
||||
def toggle_status
|
||||
@status = @conversation.toggle_status
|
||||
@@ -27,11 +19,6 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def get_messages
|
||||
@conversation = Conversation.find(params[:id])
|
||||
@messages = messages_finder.perform
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parsed_last_seen_at
|
||||
@@ -45,8 +32,4 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||
def conversation_finder
|
||||
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
||||
end
|
||||
|
||||
def messages_finder
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,55 +1,45 @@
|
||||
module Api
|
||||
module V1
|
||||
class InboxMembersController < Api::BaseController
|
||||
before_action :fetch_inbox, only: [:create, :show]
|
||||
before_action :current_agents_ids, only: [:create]
|
||||
class Api::V1::InboxMembersController < Api::BaseController
|
||||
before_action :fetch_inbox, only: [:create, :show]
|
||||
before_action :current_agents_ids, only: [:create]
|
||||
|
||||
def create
|
||||
# update also done via same action
|
||||
if @inbox
|
||||
begin
|
||||
update_agents_list
|
||||
head :ok
|
||||
rescue StandardError => e
|
||||
Rails.logger.debug "Rescued: #{e.inspect}"
|
||||
render_could_not_create_error('Could not add agents to inbox')
|
||||
end
|
||||
else
|
||||
render_not_found_error('Agents or inbox not found')
|
||||
end
|
||||
end
|
||||
def create
|
||||
# update also done via same action
|
||||
update_agents_list
|
||||
head :ok
|
||||
rescue StandardError => e
|
||||
Rails.logger.debug "Rescued: #{e.inspect}"
|
||||
render_could_not_create_error('Could not add agents to inbox')
|
||||
end
|
||||
|
||||
def show
|
||||
@agents = current_account.users.where(id: @inbox.members.pluck(:user_id))
|
||||
end
|
||||
def show
|
||||
@agents = current_account.users.where(id: @inbox.members.pluck(:user_id))
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def update_agents_list
|
||||
# get all the user_ids which the inbox currently has as members.
|
||||
# get the list of user_ids from params
|
||||
# the missing ones are the agents which are to be deleted from the inbox
|
||||
# the new ones are the agents which are to be added to the inbox
|
||||
def update_agents_list
|
||||
# get all the user_ids which the inbox currently has as members.
|
||||
# get the list of user_ids from params
|
||||
# the missing ones are the agents which are to be deleted from the inbox
|
||||
# the new ones are the agents which are to be added to the inbox
|
||||
|
||||
agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
|
||||
agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
|
||||
end
|
||||
agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
|
||||
agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
|
||||
end
|
||||
|
||||
def agents_to_be_added_ids
|
||||
params[:user_ids] - @current_agents_ids
|
||||
end
|
||||
def agents_to_be_added_ids
|
||||
params[:user_ids] - @current_agents_ids
|
||||
end
|
||||
|
||||
def agents_to_be_removed_ids
|
||||
@current_agents_ids - params[:user_ids]
|
||||
end
|
||||
def agents_to_be_removed_ids
|
||||
@current_agents_ids - params[:user_ids]
|
||||
end
|
||||
|
||||
def current_agents_ids
|
||||
@current_agents_ids = @inbox.members.pluck(:id)
|
||||
end
|
||||
def current_agents_ids
|
||||
@current_agents_ids = @inbox.members.pluck(:id)
|
||||
end
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V1::InboxesController < Api::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_inbox, only: [:destroy]
|
||||
before_action :fetch_inbox, only: [:destroy, :update]
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(current_account.inboxes)
|
||||
@@ -11,6 +11,10 @@ class Api::V1::InboxesController < Api::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@inbox.update(inbox_update_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_inbox
|
||||
@@ -20,4 +24,8 @@ class Api::V1::InboxesController < Api::BaseController
|
||||
def check_authorization
|
||||
authorize(Inbox)
|
||||
end
|
||||
|
||||
def inbox_update_params
|
||||
params.require(:inbox).permit(:enable_auto_assignment)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,4 +2,8 @@ class Api::V1::LabelsController < Api::BaseController
|
||||
def index # list all labels in account
|
||||
@labels = current_account.all_conversation_tags
|
||||
end
|
||||
|
||||
def most_used
|
||||
@labels = ActsAsTaggableOn::Tag.most_used(params[:count] || 10)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::ProfilesController < Api::BaseController
|
||||
before_action :fetch_user
|
||||
before_action :set_user
|
||||
|
||||
def show
|
||||
render json: @user
|
||||
@@ -7,12 +7,11 @@ class Api::V1::ProfilesController < Api::BaseController
|
||||
|
||||
def update
|
||||
@user.update!(profile_params)
|
||||
render json: @user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_user
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Api::V1::SubscriptionsController < ApplicationController
|
||||
class Api::V1::SubscriptionsController < Api::BaseController
|
||||
skip_before_action :check_subscription
|
||||
|
||||
before_action :check_billing_enabled
|
||||
|
||||
def index
|
||||
render json: current_account.subscription_data
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
class Api::V1::User::NotificationSettingsController < Api::BaseController
|
||||
before_action :set_user, :load_notification_setting
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
update_flags
|
||||
@notification_setting.save!
|
||||
render action: 'show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def load_notification_setting
|
||||
@notification_setting = @user.notification_settings.find_by(account_id: current_account.id)
|
||||
end
|
||||
|
||||
def notification_setting_params
|
||||
params.require(:notification_settings).permit(selected_email_flags: [])
|
||||
end
|
||||
|
||||
def update_flags
|
||||
@notification_setting.selected_email_flags = notification_setting_params[:selected_email_flags]
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,8 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
skip_before_action :set_current_user
|
||||
skip_before_action :check_subscription
|
||||
|
||||
before_action :login_from_basic_auth
|
||||
before_action :login_from_basic_auth, only: [:chargebee]
|
||||
before_action :check_billing_enabled, only: [:chargebee]
|
||||
def chargebee
|
||||
chargebee_consumer.consume
|
||||
head :ok
|
||||
@@ -12,8 +13,26 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def twitter_crc
|
||||
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
|
||||
end
|
||||
|
||||
def twitter_events
|
||||
twitter_consumer.consume
|
||||
head :ok
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def twitter_client
|
||||
Twitty::Facade.new do |config|
|
||||
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
|
||||
end
|
||||
end
|
||||
|
||||
def login_from_basic_auth
|
||||
authenticate_or_request_with_http_basic do |username, password|
|
||||
username == ENV['CHARGEBEE_WEBHOOK_USERNAME'] && password == ENV['CHARGEBEE_WEBHOOK_PASSWORD']
|
||||
@@ -21,6 +40,10 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
end
|
||||
|
||||
def chargebee_consumer
|
||||
@consumer ||= ::Webhooks::Chargebee.new(params)
|
||||
@chargebee_consumer ||= ::Webhooks::Chargebee.new(params)
|
||||
end
|
||||
|
||||
def twitter_consumer
|
||||
@twitter_consumer ||= ::Webhooks::Twitter.new(params)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
class Api::V1::Widget::BaseController < ApplicationController
|
||||
private
|
||||
|
||||
def conversation
|
||||
@conversation ||= @contact_inbox.conversations.find_by(
|
||||
inbox_id: auth_token_params[:inbox_id]
|
||||
)
|
||||
end
|
||||
|
||||
def auth_token_params
|
||||
@auth_token_params ||= ::Widget::TokenService.new(token: request.headers[header_name]).decode_token
|
||||
end
|
||||
|
||||
def header_name
|
||||
'X-Auth-Token'
|
||||
end
|
||||
|
||||
def set_web_widget
|
||||
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
|
||||
@account = @web_widget.account
|
||||
end
|
||||
|
||||
def set_contact
|
||||
@contact_inbox = @web_widget.inbox.contact_inboxes.find_by(
|
||||
source_id: auth_token_params[:source_id]
|
||||
)
|
||||
@contact = @contact_inbox.contact
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
|
||||
def index
|
||||
@inbox_members = @web_widget.inbox.inbox_members.includes(:user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permitted_params
|
||||
params.permit(:website_token)
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,8 @@
|
||||
class Api::V1::Widget::MessagesController < ActionController::Base
|
||||
skip_before_action :verify_authenticity_token
|
||||
class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
before_action :set_conversation, only: [:create]
|
||||
before_action :set_message, only: [:update]
|
||||
|
||||
def index
|
||||
@messages = conversation.nil? ? [] : message_finder.perform
|
||||
@@ -9,17 +11,19 @@ class Api::V1::Widget::MessagesController < ActionController::Base
|
||||
def create
|
||||
@message = conversation.messages.new(message_params)
|
||||
@message.save!
|
||||
render json: @message
|
||||
end
|
||||
|
||||
def update
|
||||
@message.update!(input_submitted_email: contact_email)
|
||||
update_contact(contact_email)
|
||||
head :no_content
|
||||
rescue StandardError => e
|
||||
render json: { error: @contact.errors, message: e.message }.to_json, status: 500
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conversation
|
||||
@conversation ||= ::Conversation.find_by(
|
||||
contact_id: cookie_params[:contact_id],
|
||||
inbox_id: cookie_params[:inbox_id]
|
||||
)
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation = ::Conversation.create!(conversation_params) if conversation.nil?
|
||||
end
|
||||
@@ -27,9 +31,10 @@ class Api::V1::Widget::MessagesController < ActionController::Base
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
contact_id: @contact.id,
|
||||
content: permitted_params[:message][:content],
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :incoming,
|
||||
content: permitted_params[:message][:content]
|
||||
message_type: :incoming
|
||||
}
|
||||
end
|
||||
|
||||
@@ -37,7 +42,8 @@ class Api::V1::Widget::MessagesController < ActionController::Base
|
||||
{
|
||||
account_id: inbox.account_id,
|
||||
inbox_id: inbox.id,
|
||||
contact_id: cookie_params[:contact_id],
|
||||
contact_id: @contact.id,
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
additional_attributes: {
|
||||
browser: browser_params,
|
||||
referer: permitted_params[:message][:referer_url],
|
||||
@@ -63,13 +69,7 @@ class Api::V1::Widget::MessagesController < ActionController::Base
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= ::Inbox.find_by(id: cookie_params[:inbox_id])
|
||||
end
|
||||
|
||||
def cookie_params
|
||||
@cookie_params ||= JWT.decode(
|
||||
request.headers[header_name], secret_key, true, algorithm: 'HS256'
|
||||
).first.symbolize_keys
|
||||
@inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
|
||||
end
|
||||
|
||||
def message_finder_params
|
||||
@@ -83,15 +83,31 @@ class Api::V1::Widget::MessagesController < ActionController::Base
|
||||
@message_finder ||= MessageFinder.new(conversation, message_finder_params)
|
||||
end
|
||||
|
||||
def header_name
|
||||
'X-Auth-Token'
|
||||
def update_contact(email)
|
||||
contact_with_email = @account.contacts.find_by(email: email)
|
||||
if contact_with_email
|
||||
::ContactMergeAction.new(account: @account, base_contact: contact_with_email, mergee_contact: @contact).perform
|
||||
else
|
||||
@contact.update!(
|
||||
email: email,
|
||||
name: contact_name
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def contact_email
|
||||
permitted_params[:contact][:email].downcase
|
||||
end
|
||||
|
||||
def contact_name
|
||||
contact_email.split('@')[0]
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:before, message: [:content, :referer_url, :timestamp])
|
||||
params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp])
|
||||
end
|
||||
|
||||
def secret_key
|
||||
Rails.application.secrets.secret_key_base
|
||||
def set_message
|
||||
@message = @web_widget.inbox.messages.find(permitted_params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,7 +67,7 @@ class ApplicationController < ActionController::Base
|
||||
def check_subscription
|
||||
# This block is left over from the initial version of chatwoot
|
||||
# We might reuse this later in the hosted version of chatwoot.
|
||||
return unless ENV['BILLING_ENABLED']
|
||||
return if !ENV['BILLING_ENABLED'] || !current_user
|
||||
|
||||
if current_subscription.trial? && current_subscription.expiry < Date.current
|
||||
render json: { error: 'Trial Expired' }, status: :trial_expired
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
class ConfirmationsController < Devise::ConfirmationsController
|
||||
class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController
|
||||
skip_before_action :require_no_authentication, raise: false
|
||||
skip_before_action :authenticate_user!, raise: false
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
class PasswordsController < Devise::PasswordsController
|
||||
class DeviseOverrides::PasswordsController < Devise::PasswordsController
|
||||
include AuthHelper
|
||||
|
||||
skip_before_action :require_no_authentication, raise: false
|
||||
@@ -0,0 +1,9 @@
|
||||
class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsController
|
||||
# Prevent session parameter from being passed
|
||||
# Unpermitted parameter: session
|
||||
wrap_parameters format: []
|
||||
|
||||
def render_create_success
|
||||
render 'devise/auth.json'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
class DeviseOverrides::TokenValidationsController < ::DeviseTokenAuth::TokenValidationsController
|
||||
def validate_token
|
||||
# @resource will have been set by set_user_by_token concern
|
||||
if @resource
|
||||
render 'devise/token.json'
|
||||
else
|
||||
render_validate_token_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +1,9 @@
|
||||
require 'rest-client'
|
||||
require 'telegram/bot'
|
||||
|
||||
class HomeController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token, only: [:telegram]
|
||||
skip_before_action :authenticate_user!, only: [:telegram], raise: false
|
||||
skip_before_action :set_current_user
|
||||
skip_before_action :check_subscription
|
||||
def index; end
|
||||
|
||||
def status
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
class SessionsController < ::DeviseTokenAuth::SessionsController
|
||||
# Prevent session parameter from being passed
|
||||
# Unpermitted parameter: session
|
||||
wrap_parameters format: []
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
class SwaggerController < ApplicationController
|
||||
def respond
|
||||
if Rails.env.development? || Rails.env.test?
|
||||
render inline: File.read(Rails.root.join('swagger', derived_path))
|
||||
else
|
||||
head 404
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def derived_path
|
||||
params[:path] ||= 'index.html'
|
||||
path = params[:path]
|
||||
path << ".#{params[:format]}" unless path.ends_with?(params[:format].to_s)
|
||||
path
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
class Twitter::AuthorizationsController < Twitter::BaseController
|
||||
def create
|
||||
@response = twitter_client.request_oauth_token(url: twitter_callback_url)
|
||||
|
||||
if @response.status == '200'
|
||||
::Redis::Alfred.setex(oauth_token, account.id)
|
||||
redirect_to oauth_authorize_endpoint(oauth_token)
|
||||
else
|
||||
redirect_to app_new_twitter_inbox_url
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def oauth_token
|
||||
parsed_body['oauth_token']
|
||||
end
|
||||
|
||||
def user
|
||||
@user ||= User.find_by(id: params[:user_id])
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= user.account
|
||||
end
|
||||
|
||||
def oauth_authorize_endpoint(oauth_token)
|
||||
"#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
class Twitter::BaseController < ApplicationController
|
||||
private
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
|
||||
end
|
||||
|
||||
def host
|
||||
ENV.fetch('FRONTEND_URL', '')
|
||||
end
|
||||
|
||||
def twitter_client
|
||||
Twitty::Facade.new do |config|
|
||||
config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
|
||||
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
|
||||
config.base_url = twitter_api_base_url
|
||||
config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
|
||||
end
|
||||
end
|
||||
|
||||
def twitter_api_base_url
|
||||
'https://api.twitter.com'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
class Twitter::CallbacksController < Twitter::BaseController
|
||||
def show
|
||||
return redirect_to app_new_twitter_inbox_url if permitted_params[:denied]
|
||||
|
||||
@response = twitter_client.access_token(
|
||||
oauth_token: permitted_params[:oauth_token],
|
||||
oauth_verifier: permitted_params[:oauth_verifier]
|
||||
)
|
||||
if @response.status == '200'
|
||||
inbox = build_inbox
|
||||
::Redis::Alfred.delete(permitted_params[:oauth_token])
|
||||
::Twitter::WebhookSubscribeService.new(inbox_id: inbox.id).perform
|
||||
redirect_to app_twitter_inbox_agents_url(inbox_id: inbox.id)
|
||||
else
|
||||
redirect_to app_new_twitter_inbox_url
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
|
||||
end
|
||||
|
||||
def account_id
|
||||
::Redis::Alfred.get(permitted_params[:oauth_token])
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= Account.find_by!(id: account_id)
|
||||
end
|
||||
|
||||
def build_inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
twitter_profile = account.twitter_profiles.create(
|
||||
twitter_access_token: parsed_body['oauth_token'],
|
||||
twitter_access_token_secret: parsed_body['oauth_token_secret'],
|
||||
profile_id: parsed_body['user_id'],
|
||||
name: parsed_body['screen_name']
|
||||
)
|
||||
account.inboxes.create(
|
||||
name: parsed_body['screen_name'],
|
||||
channel: twitter_profile
|
||||
)
|
||||
rescue StandardError => e
|
||||
Rails.logger e
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:oauth_token, :oauth_verifier, :denied)
|
||||
end
|
||||
end
|
||||
@@ -4,66 +4,45 @@ class WidgetsController < ActionController::Base
|
||||
before_action :set_contact
|
||||
before_action :build_contact
|
||||
|
||||
def index; end
|
||||
|
||||
private
|
||||
|
||||
def set_contact
|
||||
return if cookie_params[:source_id].nil?
|
||||
|
||||
contact_inbox = ::ContactInbox.find_by(
|
||||
inbox_id: @web_widget.inbox.id,
|
||||
source_id: cookie_params[:source_id]
|
||||
)
|
||||
|
||||
@contact = contact_inbox ? contact_inbox.contact : nil
|
||||
end
|
||||
|
||||
def set_token
|
||||
@token = conversation_token
|
||||
end
|
||||
|
||||
def set_web_widget
|
||||
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
|
||||
end
|
||||
|
||||
def set_token
|
||||
@token = permitted_params[:cw_conversation]
|
||||
@auth_token_params = if @token.present?
|
||||
::Widget::TokenService.new(token: @token).decode_token
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def set_contact
|
||||
return if @auth_token_params[:source_id].nil?
|
||||
|
||||
contact_inbox = ::ContactInbox.find_by(
|
||||
inbox_id: @web_widget.inbox.id,
|
||||
source_id: @auth_token_params[:source_id]
|
||||
)
|
||||
|
||||
@contact = contact_inbox ? contact_inbox.contact : nil
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if @contact.present?
|
||||
|
||||
contact_inbox = @web_widget.create_contact_inbox
|
||||
@contact = contact_inbox.contact
|
||||
|
||||
payload = {
|
||||
source_id: contact_inbox.source_id,
|
||||
contact_id: @contact.id,
|
||||
inbox_id: @web_widget.inbox.id
|
||||
}
|
||||
@token = JWT.encode payload, secret_key, 'HS256'
|
||||
end
|
||||
|
||||
def cookie_params
|
||||
return @cookie_params if @cookie_params.present?
|
||||
|
||||
if conversation_token.present?
|
||||
begin
|
||||
@cookie_params = JWT.decode(
|
||||
conversation_token, secret_key, true, algorithm: 'HS256'
|
||||
).first.symbolize_keys
|
||||
rescue StandardError
|
||||
@cookie_params = {}
|
||||
end
|
||||
return @cookie_params
|
||||
end
|
||||
{}
|
||||
end
|
||||
|
||||
def conversation_token
|
||||
permitted_params[:cw_conversation]
|
||||
payload = { source_id: contact_inbox.source_id, inbox_id: @web_widget.inbox.id }
|
||||
@token = ::Widget::TokenService.new(payload: payload).generate_token
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:website_token, :cw_conversation)
|
||||
end
|
||||
|
||||
def secret_key
|
||||
Rails.application.secrets.secret_key_base
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ class AsyncDispatcher < BaseDispatcher
|
||||
end
|
||||
|
||||
def listeners
|
||||
listeners = [ReportingListener.instance]
|
||||
listeners = [EmailNotificationListener.instance, ReportingListener.instance, WebhookListener.instance]
|
||||
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
||||
listeners
|
||||
end
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
class ConversationFinder
|
||||
attr_reader :current_user, :current_account, :params
|
||||
|
||||
ASSIGNEE_TYPES = { me: 0, unassigned: 1, all: 2 }.freeze
|
||||
|
||||
ASSIGNEE_TYPES_BY_ID = ASSIGNEE_TYPES.invert
|
||||
ASSIGNEE_TYPES_BY_ID.default = :me
|
||||
|
||||
DEFAULT_STATUS = 'open'.freeze
|
||||
|
||||
# assumptions
|
||||
# inbox_id if not given, take from all conversations, else specific to inbox
|
||||
# assignee_type if not given, take 'me'
|
||||
# assignee_type if not given, take 'all'
|
||||
# conversation_status if not given, take 'open'
|
||||
|
||||
# response of this class will be of type
|
||||
# {conversations: [array of conversations], count: {open: count, resolved: count}}
|
||||
|
||||
# params
|
||||
# assignee_type_id, inbox_id, :status
|
||||
# assignee_type, inbox_id, :status
|
||||
|
||||
def initialize(current_user, params)
|
||||
@current_user = current_user
|
||||
@@ -31,6 +26,7 @@ class ConversationFinder
|
||||
|
||||
find_all_conversations
|
||||
filter_by_status
|
||||
filter_by_labels if params[:labels]
|
||||
|
||||
mine_count, unassigned_count, all_count = set_count_for_all_conversations
|
||||
|
||||
@@ -61,8 +57,7 @@ class ConversationFinder
|
||||
end
|
||||
|
||||
def set_assignee_type
|
||||
@assignee_type_id = ASSIGNEE_TYPES[ASSIGNEE_TYPES_BY_ID[params[:assignee_type_id].to_i]]
|
||||
# ente budhiparamaya neekam kandit enthu tonunu? ;)
|
||||
@assignee_type = params[:assignee_type]
|
||||
end
|
||||
|
||||
def find_all_conversations
|
||||
@@ -72,12 +67,10 @@ class ConversationFinder
|
||||
end
|
||||
|
||||
def filter_by_assignee_type
|
||||
if @assignee_type_id == ASSIGNEE_TYPES[:me]
|
||||
if @assignee_type == 'me'
|
||||
@conversations = @conversations.assigned_to(current_user)
|
||||
elsif @assignee_type_id == ASSIGNEE_TYPES[:unassigned]
|
||||
elsif @assignee_type == 'unassigned'
|
||||
@conversations = @conversations.unassigned
|
||||
elsif @assignee_type_id == ASSIGNEE_TYPES[:all]
|
||||
@conversations
|
||||
end
|
||||
@conversations
|
||||
end
|
||||
@@ -86,6 +79,10 @@ class ConversationFinder
|
||||
@conversations = @conversations.where(status: params[:status] || DEFAULT_STATUS)
|
||||
end
|
||||
|
||||
def filter_by_labels
|
||||
@conversations = @conversations.tagged_with(params[:labels], any: true)
|
||||
end
|
||||
|
||||
def set_count_for_all_conversations
|
||||
[
|
||||
@conversations.assigned_to(current_user).count,
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
module Api::V1::WebhooksHelper
|
||||
end
|
||||
@@ -18,8 +18,7 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$store.dispatch('set_user');
|
||||
this.$store.dispatch('validityCheck');
|
||||
this.$store.dispatch('setUser');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint-env browser */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
|
||||
import moment from 'moment';
|
||||
import Cookies from 'js-cookie';
|
||||
import endPoints from './endPoints';
|
||||
import { frontendURL } from '../helper/URLHelper';
|
||||
|
||||
const setAuthCredentials = response => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
Cookies.set('user', response.data.data, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
};
|
||||
|
||||
const clearCookiesOnLogout = () => {
|
||||
Cookies.remove('auth_data');
|
||||
Cookies.remove('user');
|
||||
window.location = frontendURL('login');
|
||||
};
|
||||
import { setAuthCredentials, clearCookiesOnLogout } from '../store/utils/api';
|
||||
|
||||
export default {
|
||||
login(creds) {
|
||||
@@ -60,20 +41,7 @@ export default {
|
||||
},
|
||||
validityCheck() {
|
||||
const urlData = endPoints('validityCheck');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.status === 401) {
|
||||
clearCookiesOnLogout();
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
return axios.get(urlData.url);
|
||||
},
|
||||
logout() {
|
||||
const urlData = endPoints('logout');
|
||||
@@ -136,13 +104,7 @@ export default {
|
||||
password,
|
||||
})
|
||||
.then(response => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
Cookies.set('user', response.data.data, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
setAuthCredentials(response);
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -155,4 +117,22 @@ export default {
|
||||
const urlData = endPoints('resetPassword');
|
||||
return axios.post(urlData.url, { email });
|
||||
},
|
||||
|
||||
profileUpdate({ name, email, password, password_confirmation, avatar }) {
|
||||
const formData = new FormData();
|
||||
if (name) {
|
||||
formData.append('profile[name]', name);
|
||||
}
|
||||
if (email) {
|
||||
formData.append('profile[email]', email);
|
||||
}
|
||||
if (password && password_confirmation) {
|
||||
formData.append('profile[password]', password);
|
||||
formData.append('profile[password_confirmation]', password_confirmation);
|
||||
}
|
||||
if (avatar) {
|
||||
formData.append('profile[avatar]', avatar);
|
||||
}
|
||||
return axios.put(endPoints('profileUpdate').url, formData);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class ContactAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('contacts');
|
||||
}
|
||||
|
||||
getConversations(contactId) {
|
||||
return axios.get(`${this.url}/${contactId}/conversations`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ContactAPI();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class ConversationApi extends ApiClient {
|
||||
constructor() {
|
||||
super('conversations');
|
||||
}
|
||||
|
||||
getLabels(conversationID) {
|
||||
return axios.get(`${this.url}/${conversationID}/labels`);
|
||||
}
|
||||
|
||||
updateLabels(conversationID, labels) {
|
||||
return axios.post(`${this.url}/${conversationID}/labels`, { labels });
|
||||
}
|
||||
}
|
||||
|
||||
export default new ConversationApi();
|
||||
@@ -10,6 +10,9 @@ const endPoints = {
|
||||
validityCheck: {
|
||||
url: '/auth/validate_token',
|
||||
},
|
||||
profileUpdate: {
|
||||
url: '/api/v1/profile',
|
||||
},
|
||||
logout: {
|
||||
url: 'auth/sign_out',
|
||||
},
|
||||
|
||||
@@ -6,12 +6,13 @@ class ConversationApi extends ApiClient {
|
||||
super('conversations');
|
||||
}
|
||||
|
||||
get({ inboxId, status, assigneeType }) {
|
||||
get({ inboxId, status, assigneeType, page }) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
inbox_id: inboxId,
|
||||
status,
|
||||
assignee_type_id: assigneeType,
|
||||
assignee_type: assigneeType,
|
||||
page,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class MessageApi extends ApiClient {
|
||||
}
|
||||
|
||||
getPreviousMessages({ conversationId, before }) {
|
||||
return axios.get(`${this.url}/${conversationId}`, {
|
||||
return axios.get(`${this.url}/${conversationId}/messages`, {
|
||||
params: { before },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import agents from '../contacts';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#ContactsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(agents).toBeInstanceOf(ApiClient);
|
||||
expect(agents).toHaveProperty('get');
|
||||
expect(agents).toHaveProperty('show');
|
||||
expect(agents).toHaveProperty('create');
|
||||
expect(agents).toHaveProperty('update');
|
||||
expect(agents).toHaveProperty('delete');
|
||||
expect(agents).toHaveProperty('getConversations');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import conversations from '../conversations';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#ConversationApi', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(conversations).toBeInstanceOf(ApiClient);
|
||||
expect(conversations).toHaveProperty('get');
|
||||
expect(conversations).toHaveProperty('show');
|
||||
expect(conversations).toHaveProperty('create');
|
||||
expect(conversations).toHaveProperty('update');
|
||||
expect(conversations).toHaveProperty('delete');
|
||||
expect(conversations).toHaveProperty('getLabels');
|
||||
expect(conversations).toHaveProperty('updateLabels');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import userNotificationSettings from '../userNotificationSettings';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#AgentAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(userNotificationSettings).toBeInstanceOf(ApiClient);
|
||||
expect(userNotificationSettings).toHaveProperty('get');
|
||||
expect(userNotificationSettings).toHaveProperty('show');
|
||||
expect(userNotificationSettings).toHaveProperty('create');
|
||||
expect(userNotificationSettings).toHaveProperty('update');
|
||||
expect(userNotificationSettings).toHaveProperty('delete');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class UserNotificationSettings extends ApiClient {
|
||||
constructor() {
|
||||
super('user/notification_settings');
|
||||
}
|
||||
|
||||
update(params) {
|
||||
return axios.patch(`${this.url}`, params);
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserNotificationSettings();
|
||||
@@ -0,0 +1,9 @@
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class WebHooks extends ApiClient {
|
||||
constructor() {
|
||||
super('account/webhooks');
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebHooks();
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#4D4D4D;" d="M188.287,512c-41.473,0-75.213-33.74-75.213-75.213V246.75c0-4.142,3.358-7.5,7.5-7.5
|
||||
s7.5,3.358,7.5,7.5v190.037c0,33.202,27.011,60.213,60.213,60.213c16.082,0,31.204-6.266,42.582-17.644
|
||||
c11.37-11.37,17.631-26.488,17.631-42.569V75.213C248.5,33.74,282.24,0,323.713,0c20.088,0,38.978,7.826,53.189,22.037
|
||||
c14.203,14.202,22.024,33.087,22.024,53.176V256c0,4.142-3.358,7.5-7.5,7.5s-7.5-3.358-7.5-7.5V75.213
|
||||
c0-16.082-6.261-31.2-17.63-42.569C354.918,21.266,339.794,15,323.713,15C290.511,15,263.5,42.011,263.5,75.213v361.574
|
||||
c0,20.088-7.822,38.973-22.024,53.176C227.265,504.174,208.376,512,188.287,512z"/>
|
||||
<g>
|
||||
<rect x="113.07" y="246.75" style="fill:#3B3B3B;" width="15" height="26.875"/>
|
||||
<rect x="383.93" y="235.31" style="fill:#3B3B3B;" width="15" height="26.875"/>
|
||||
</g>
|
||||
<rect x="361.9" y="385" style="fill:#CCCCCC;" width="57.983" height="39.944"/>
|
||||
<rect x="361.9" y="385" style="fill:#ADADAD;" width="57.983" height="22.19"/>
|
||||
<path style="fill:#A6E2E3;" d="M432.802,298.678v86.977c0,3.616-2.932,6.548-6.548,6.548h-70.721c-3.617,0-6.548-2.932-6.548-6.548
|
||||
v-87.746c0-23.439,19.239-42.39,42.803-41.899C414.709,256.486,432.802,275.751,432.802,298.678z"/>
|
||||
<rect x="92.11" y="87.06" style="fill:#CCCCCC;" width="57.983" height="36.28"/>
|
||||
<rect x="92.11" y="105.43" style="fill:#ADADAD;" width="57.983" height="17.907"/>
|
||||
<path style="fill:#FFA638;" d="M163.015,126.345v86.977c0,22.927-18.093,42.191-41.015,42.668
|
||||
c-23.564,0.49-42.803-18.461-42.803-41.899v-87.746c0-3.616,2.932-6.548,6.548-6.548l0,0h70.721l0,0
|
||||
C160.083,119.797,163.015,122.729,163.015,126.345z"/>
|
||||
<path style="fill:#7CCBCC;" d="M391.787,256.009c-5.066-0.105-9.93,0.693-14.447,2.236c0.396-0.081,0.781-0.166,1.142-0.257
|
||||
c2.982-0.755,5.201-0.896,7.513-0.85c18.954,0.395,34.375,16.494,34.375,35.888v86.981c0,3.614-2.93,6.544-6.544,6.544H355.53
|
||||
c-3.614,0-6.544-2.93-6.544-6.544l0,0v5.648c0,3.616,2.932,6.548,6.548,6.548h70.721c3.617,0,6.548-2.932,6.548-6.548v-86.977
|
||||
C432.802,275.751,414.709,256.486,391.787,256.009z"/>
|
||||
<path style="fill:#EB7100;" d="M79.527,217.153l-0.23-0.322c0.081,1.261,0.209,2.509,0.399,3.737
|
||||
C79.586,219.444,79.527,218.305,79.527,217.153z"/>
|
||||
<path style="fill:#ED8300;" d="M156.467,119.797h-11.188c2.613,0.858,4.502,3.314,4.502,6.215v90.372
|
||||
c0,19.395-15.42,35.494-34.375,35.888c-0.251,0.005-0.503,0.008-0.753,0.008c-9.651,0-18.405-3.914-24.76-10.236
|
||||
c7.856,8.766,19.342,14.212,32.106,13.947c22.922-0.477,41.015-19.741,41.015-42.668v-86.977
|
||||
C163.015,122.728,160.083,119.797,156.467,119.797z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -28,7 +28,7 @@
|
||||
|
||||
code {
|
||||
border: 0;
|
||||
font-family: 'Monaco';
|
||||
font-family: 'Monaco', Verdana;
|
||||
font-size: $font-size-mini;
|
||||
|
||||
&.hljs {
|
||||
|
||||
@@ -41,29 +41,34 @@
|
||||
// 36. Tooltip
|
||||
// 37. Top Bar
|
||||
|
||||
@import "~foundation-sites/scss/util/util";
|
||||
@import '~foundation-sites/scss/util/util';
|
||||
// 1. Global
|
||||
// ---------
|
||||
|
||||
$global-font-size: 10px;
|
||||
$global-width: 100%;
|
||||
$global-lineheight: 1.5;
|
||||
$foundation-palette: (
|
||||
primary: $color-woot,
|
||||
$foundation-palette: (primary: $color-woot,
|
||||
secondary: #777,
|
||||
success: #13ce66,
|
||||
warning: #ffc82c,
|
||||
alert: #ff4949
|
||||
);
|
||||
alert: #ff4949);
|
||||
$light-gray: #c0ccda;
|
||||
$medium-gray: #8492a6;
|
||||
$dark-gray: $color-gray;
|
||||
$black: #000000;
|
||||
$black: #000;
|
||||
$white: #fff;
|
||||
$body-background: $white;
|
||||
$body-font-color: $color-body;
|
||||
$body-font-family: 'Inter', -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
$body-font-family: 'Inter',
|
||||
-apple-system,
|
||||
system-ui,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
sans-serif;
|
||||
$body-antialiased: true;
|
||||
$global-margin: $space-one;
|
||||
$global-padding: $space-one;
|
||||
@@ -79,13 +84,11 @@ $print-transparent-backgrounds: true;
|
||||
// 2. Breakpoints
|
||||
// --------------
|
||||
|
||||
$breakpoints: (
|
||||
small: 0,
|
||||
$breakpoints: (small: 0,
|
||||
medium: 640px,
|
||||
large: 1024px,
|
||||
xlarge: 1200px,
|
||||
xxlarge: 1440px
|
||||
);
|
||||
xxlarge: 1440px);
|
||||
$print-breakpoint: large;
|
||||
$breakpoint-classes: (small medium large);
|
||||
|
||||
@@ -94,10 +97,8 @@ $breakpoint-classes: (small medium large);
|
||||
|
||||
$grid-row-width: $global-width;
|
||||
$grid-column-count: 12;
|
||||
$grid-column-gutter: (
|
||||
small: $zero,
|
||||
medium: $zero
|
||||
);
|
||||
$grid-column-gutter: (small: $zero,
|
||||
medium: $zero);
|
||||
$grid-column-align-edge: true;
|
||||
$block-grid-max: 8;
|
||||
|
||||
@@ -105,54 +106,24 @@ $block-grid-max: 8;
|
||||
// ------------------
|
||||
|
||||
$header-font-family: $body-font-family;
|
||||
$header-font-weight: $global-weight-normal;
|
||||
$header-font-weight: $font-weight-medium;
|
||||
$header-font-style: normal;
|
||||
$font-family-monospace: $body-font-family;
|
||||
$header-color: $color-heading;
|
||||
$header-lineheight: 1.4;
|
||||
$header-margin-bottom: 0.5rem;
|
||||
$header-styles: (
|
||||
small: (
|
||||
"h1": (
|
||||
"font-size": 24
|
||||
),
|
||||
"h2": (
|
||||
"font-size": 20
|
||||
),
|
||||
"h3": (
|
||||
"font-size": 19
|
||||
),
|
||||
"h4": (
|
||||
"font-size": 18
|
||||
),
|
||||
"h5": (
|
||||
"font-size": 17
|
||||
),
|
||||
"h6": (
|
||||
"font-size": 16
|
||||
)
|
||||
),
|
||||
medium: (
|
||||
"h1": (
|
||||
"font-size": 48
|
||||
),
|
||||
"h2": (
|
||||
"font-size": 40
|
||||
),
|
||||
"h3": (
|
||||
"font-size": 31
|
||||
),
|
||||
"h4": (
|
||||
"font-size": 25
|
||||
),
|
||||
"h5": (
|
||||
"font-size": 20
|
||||
),
|
||||
"h6": (
|
||||
"font-size": 16
|
||||
)
|
||||
)
|
||||
);
|
||||
$header-styles: (small: ("h1": ("font-size": 24),
|
||||
"h2": ("font-size": 20),
|
||||
"h3": ("font-size": 19),
|
||||
"h4": ("font-size": 18),
|
||||
"h5": ("font-size": 17),
|
||||
"h6": ("font-size": 16)),
|
||||
medium: ("h1": ("font-size": 48),
|
||||
"h2": ("font-size": 40),
|
||||
"h3": ("font-size": 31),
|
||||
"h4": ("font-size": 25),
|
||||
"h5": ("font-size": 20),
|
||||
"h6": ("font-size": 16)));
|
||||
$header-text-rendering: optimizeLegibility;
|
||||
$small-font-size: 80%;
|
||||
$header-small-font-color: $medium-gray;
|
||||
@@ -186,7 +157,7 @@ $blockquote-padding: rem-calc(9 20 0 19);
|
||||
$blockquote-border: 1px solid $medium-gray;
|
||||
$cite-font-size: rem-calc(13);
|
||||
$cite-color: $dark-gray;
|
||||
$cite-pseudo-content: "\2014 \0020";
|
||||
$cite-pseudo-content: '\2014 \0020';
|
||||
$keystroke-font: $font-family-monospace;
|
||||
$keystroke-color: $black;
|
||||
$keystroke-background: $light-gray;
|
||||
@@ -272,24 +243,23 @@ $button-background-hover: scale-color($button-background, $lightness: -15%);
|
||||
$button-color: $white;
|
||||
$button-color-alt: $white;
|
||||
$button-radius: $global-radius;
|
||||
$button-sizes: (
|
||||
tiny: $font-size-micro,
|
||||
$button-sizes: (tiny: $font-size-micro,
|
||||
small: $font-size-mini,
|
||||
default: $font-size-default,
|
||||
large: $font-size-large
|
||||
);
|
||||
large: $font-size-large);
|
||||
$button-palette: $foundation-palette;
|
||||
$button-opacity-disabled: 0.25;
|
||||
$button-background-hover-lightness: -20%;
|
||||
$button-hollow-hover-lightness: -50%;
|
||||
$button-transition: background-color 0.25s ease-out, color 0.25s ease-out;
|
||||
$button-transition: background-color 0.25s ease-out,
|
||||
color 0.25s ease-out;
|
||||
|
||||
// 12. Button Group
|
||||
// ----------------
|
||||
|
||||
$buttongroup-margin: 1rem;
|
||||
$buttongroup-spacing: 1px;
|
||||
$buttongroup-child-selector: ".button";
|
||||
$buttongroup-child-selector: '.button';
|
||||
$buttongroup-expand-max: 6;
|
||||
$buttongroup-radius-on-each: true;
|
||||
|
||||
@@ -322,18 +292,12 @@ $card-margin: $global-margin;
|
||||
// ----------------
|
||||
|
||||
$closebutton-position: right top;
|
||||
$closebutton-offset-horizontal: (
|
||||
small: 0.66rem,
|
||||
medium: 1rem
|
||||
);
|
||||
$closebutton-offset-vertical: (
|
||||
small: 0.33em,
|
||||
medium: 0.5rem
|
||||
);
|
||||
$closebutton-size: (
|
||||
small: 1.5em,
|
||||
medium: 2em
|
||||
);
|
||||
$closebutton-offset-horizontal: (small: 0.66rem,
|
||||
medium: 1rem);
|
||||
$closebutton-offset-vertical: (small: 0.33em,
|
||||
medium: 0.5rem);
|
||||
$closebutton-size: (small: 1.5em,
|
||||
medium: 2em);
|
||||
$closebutton-lineheight: 1;
|
||||
$closebutton-color: $dark-gray;
|
||||
$closebutton-color-hover: $black;
|
||||
@@ -356,11 +320,9 @@ $dropdown-border: 1px solid $medium-gray;
|
||||
$dropdown-font-size: 1rem;
|
||||
$dropdown-width: 300px;
|
||||
$dropdown-radius: $global-radius;
|
||||
$dropdown-sizes: (
|
||||
tiny: 100px,
|
||||
$dropdown-sizes: (tiny: 100px,
|
||||
small: 200px,
|
||||
large: 400px
|
||||
);
|
||||
large: 400px);
|
||||
|
||||
// 18. Dropdown Menu
|
||||
// -----------------
|
||||
@@ -455,12 +417,10 @@ $meter-fill-bad: $alert-color;
|
||||
// 24. Off-canvas
|
||||
// --------------
|
||||
|
||||
$offcanvas-sizes: (
|
||||
small: 23rem,
|
||||
$offcanvas-sizes: (small: 23rem,
|
||||
medium: 23rem,
|
||||
);
|
||||
$offcanvas-vertical-sizes: (
|
||||
small: 23rem,
|
||||
$offcanvas-vertical-sizes: (small: 23rem,
|
||||
medium: 23rem,
|
||||
);
|
||||
$offcanvas-background: $light-gray;
|
||||
@@ -472,7 +432,7 @@ $offcanvas-transition-length: 0.5s;
|
||||
$offcanvas-transition-timing: ease;
|
||||
$offcanvas-fixed-reveal: true;
|
||||
$offcanvas-exit-background: rgba($white, 0.25);
|
||||
$maincontent-class: "off-canvas-content";
|
||||
$maincontent-class: 'off-canvas-content';
|
||||
|
||||
// 25. Orbit
|
||||
// ---------
|
||||
@@ -520,10 +480,8 @@ $progress-radius: $global-radius;
|
||||
// --------------------
|
||||
|
||||
$responsive-embed-margin-bottom: rem-calc(16);
|
||||
$responsive-embed-ratios: (
|
||||
default: 4 by 3,
|
||||
widescreen: 16 by 9
|
||||
);
|
||||
$responsive-embed-ratios: (default: 4 by 3,
|
||||
widescreen: 16 by 9);
|
||||
|
||||
// 29. Reveal
|
||||
// ----------
|
||||
@@ -576,10 +534,8 @@ $table-border: 1px solid transparent;
|
||||
$table-padding: rem-calc(8 10 10);
|
||||
$table-hover-scale: 2%;
|
||||
$table-row-hover: darken($table-background, $table-hover-scale);
|
||||
$table-row-stripe-hover: darken(
|
||||
$table-background,
|
||||
$table-color-scale + $table-hover-scale
|
||||
);
|
||||
$table-row-stripe-hover: darken($table-background,
|
||||
$table-color-scale + $table-hover-scale);
|
||||
$table-is-striped: false;
|
||||
$table-striped-background: smart-scale($table-background, $table-color-scale);
|
||||
$table-stripe: even;
|
||||
|
||||
@@ -35,15 +35,17 @@ body {
|
||||
flex-direction: column;
|
||||
@include margin($zero);
|
||||
@include padding($space-normal);
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
@include padding($space-normal);
|
||||
}
|
||||
|
||||
.back-button {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
color: $color-woot;
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-normal;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
@import '~widget/assets/scss/mixins';
|
||||
|
||||
$elegant-shadow-color: rgba(49, 49, 93, 0.15);
|
||||
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
|
||||
|
||||
//borders
|
||||
@mixin border-nil() {
|
||||
border-color: transparent;
|
||||
@@ -77,8 +82,8 @@
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +122,6 @@
|
||||
// full height
|
||||
@mixin full-height() {
|
||||
height: 100%;
|
||||
// COmmenting because unneccessary scroll is apprearing on some pages eg: settings/agents / inboxes
|
||||
}
|
||||
|
||||
@mixin round-corner() {
|
||||
@@ -125,21 +129,20 @@
|
||||
}
|
||||
|
||||
@mixin scroll-on-hover() {
|
||||
transition: all .4s $ease-in-out-cubic;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mixin horizontal-scroll() {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@mixin elegent-shadow() {
|
||||
box-shadow: 0 10px 25px 0 rgba(49,49,93,0.15);
|
||||
box-shadow: 0 10px 25px 0 $elegant-shadow-color;
|
||||
}
|
||||
|
||||
@mixin elegant-card() {
|
||||
@@ -154,20 +157,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
&::before {
|
||||
animation: spinner .9s linear infinite;
|
||||
border: 2px solid $spinner-before-border-color;
|
||||
border-radius: 50%;
|
||||
border-top-color: lighten($color-woot, 10%);
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
height: $space-medium;
|
||||
left: 50%;
|
||||
margin-left: -$space-one;
|
||||
margin-top: -$space-one;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: $space-medium;
|
||||
height: $space-medium;
|
||||
margin-top: -$space-one;
|
||||
margin-left: -$space-one;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.7);
|
||||
border-top-color: lighten($color-woot, 10%);
|
||||
animation: spinner .9s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,41 +184,41 @@
|
||||
// .element{
|
||||
// @include arrow(top, #000, 50px);
|
||||
// }
|
||||
@mixin arrow($direction, $color, $size){
|
||||
display: block;
|
||||
height: 0;
|
||||
width: 0;
|
||||
content: '';
|
||||
@mixin arrow($direction, $color, $size) {
|
||||
display: block;
|
||||
height: 0;
|
||||
width: 0;
|
||||
content: '';
|
||||
|
||||
@if $direction == 'top' {
|
||||
border-left: $size solid transparent;
|
||||
border-right: $size solid transparent;
|
||||
border-bottom: $size solid $color;
|
||||
} @else if $direction == 'right' {
|
||||
border-top: $size solid transparent;
|
||||
border-bottom: $size solid transparent;
|
||||
border-left: $size solid $color;
|
||||
} @else if $direction == 'bottom' {
|
||||
border-top: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
border-left: $size solid transparent;
|
||||
} @else if $direction == 'left' {
|
||||
border-top: $size solid transparent;
|
||||
border-right: $size solid $color;
|
||||
border-bottom: $size solid transparent;
|
||||
} @else if $direction == 'top-left' {
|
||||
border-top: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
} @else if $direction == 'top-right' {
|
||||
border-top: $size solid $color;
|
||||
border-left: $size solid transparent;
|
||||
} @else if $direction == 'bottom-left' {
|
||||
border-bottom: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
} @else if $direction == 'bottom-right' {
|
||||
border-bottom: $size solid $color;
|
||||
border-left: $size solid transparent;
|
||||
}
|
||||
@if $direction == 'top' {
|
||||
border-bottom: $size solid $color;
|
||||
border-left: $size solid transparent;
|
||||
border-right: $size solid transparent;
|
||||
} @else if $direction == 'right' {
|
||||
border-bottom: $size solid transparent;
|
||||
border-left: $size solid $color;
|
||||
border-top: $size solid transparent;
|
||||
} @else if $direction == 'bottom' {
|
||||
border-left: $size solid transparent;
|
||||
border-right: $size solid transparent;
|
||||
border-top: $size solid $color;
|
||||
} @else if $direction == 'left' {
|
||||
border-bottom: $size solid transparent;
|
||||
border-right: $size solid $color;
|
||||
border-top: $size solid transparent;
|
||||
} @else if $direction == 'top-left' {
|
||||
border-right: $size solid transparent;
|
||||
border-top: $size solid $color;
|
||||
} @else if $direction == 'top-right' {
|
||||
border-left: $size solid transparent;
|
||||
border-top: $size solid $color;
|
||||
} @else if $direction == 'bottom-left' {
|
||||
border-bottom: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
} @else if $direction == 'bottom-right' {
|
||||
border-bottom: $size solid $color;
|
||||
border-left: $size solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin text-ellipsis {
|
||||
|
||||
@@ -12,7 +12,7 @@ $font-size-mega: 3.4rem;
|
||||
$font-size-giga: 4.0rem;
|
||||
|
||||
// spaces
|
||||
$zero: 0rem;
|
||||
$zero: 0;
|
||||
$space-micro: 0.2rem;
|
||||
$space-smaller: 0.4rem;
|
||||
$space-small: 0.8rem;
|
||||
@@ -42,16 +42,27 @@ $woot-logo-padding: $space-large $space-two;
|
||||
|
||||
// Colors
|
||||
$color-woot: #1f93ff;
|
||||
$color-gray: #6E6F73;
|
||||
$color-light-gray: #999A9B;
|
||||
$color-border: #E0E6ED;
|
||||
$color-gray: #6e6f73;
|
||||
$color-light-gray: #999a9b;
|
||||
$color-border: #e0e6ed;
|
||||
$color-border-light: #f0f4f5;
|
||||
$color-background: #EFF2F7;
|
||||
$color-background-light: #F9FAFC;
|
||||
$color-white: #FFF;
|
||||
$color-body: #3C4858;
|
||||
$color-heading: #1F2D3D;
|
||||
$color-extra-light-blue: #F5F7F9;
|
||||
$color-background: #eff2f7;
|
||||
$color-background-light: #f9fafc;
|
||||
$color-white: #fff;
|
||||
$color-body: #3c4858;
|
||||
$color-heading: #1f2d3d;
|
||||
$color-extra-light-blue: #f5f7f9;
|
||||
|
||||
$primary-color: $color-woot;
|
||||
$secondary-color: #ff5216;
|
||||
$success-color: #13ce66;
|
||||
$warning-color: #ffc82c;
|
||||
$alert-color: #ff4949;
|
||||
|
||||
// Color-palettes
|
||||
|
||||
$color-primary-light: #c7e3ff;
|
||||
$color-primary-dark: darken($color-woot, 20%);
|
||||
|
||||
// Thumbnail
|
||||
$thumbnail-radius: 4rem;
|
||||
@@ -81,3 +92,6 @@ $swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !d
|
||||
|
||||
// Ionicons
|
||||
$ionicons-font-path: '~ionicons/fonts';
|
||||
|
||||
// Transitions
|
||||
$transition-ease-in: all 0.250s ease-in;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
@import 'views/settings/inbox';
|
||||
@import 'views/settings/channel';
|
||||
@import 'views/settings/integrations';
|
||||
@import 'views/signup';
|
||||
|
||||
@import 'plugins/multiselect';
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
@mixin label-multiselect-hover {
|
||||
&::after {
|
||||
color: $color-primary-dark;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-background;
|
||||
|
||||
&::after {
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin-bottom: $space-normal;
|
||||
min-height: 38px;
|
||||
|
||||
> .multiselect__tags {
|
||||
@include margin(0);
|
||||
border: 1px solid $color-border;
|
||||
min-height: 44px;
|
||||
padding-top: $zero;
|
||||
|
||||
.multiselect__placeholder {
|
||||
padding-top: $space-small;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
margin-top: $space-one;
|
||||
}
|
||||
|
||||
.multiselect__input {
|
||||
@include ghost-input;
|
||||
@include padding($zero);
|
||||
|
||||
margin-bottom: $zero;
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
@include padding($space-one);
|
||||
|
||||
margin-bottom: 0;
|
||||
&.multiselect--active {
|
||||
>.multiselect__tags {
|
||||
border-color: $color-woot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +33,93 @@
|
||||
top: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__content .multiselect__option {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-normal;
|
||||
|
||||
&.multiselect__option--highlight {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect>.multiselect__tags {
|
||||
@include margin(0);
|
||||
border: 1px solid $color-border;
|
||||
min-height: 44px;
|
||||
padding-top: $zero;
|
||||
|
||||
.multiselect__tags-wrap {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
margin-top: $space-smaller;
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
color: $color-gray;
|
||||
font-weight: $font-weight-normal;
|
||||
padding-top: $space-small;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
$vertical-space: $space-smaller + $space-micro;
|
||||
background: $color-background;
|
||||
color: $color-heading;
|
||||
margin-top: $space-smaller;
|
||||
padding: $vertical-space $space-medium $vertical-space $space-one;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon {
|
||||
@include label-multiselect-hover;
|
||||
line-height: $space-medium + $space-micro;
|
||||
}
|
||||
|
||||
.multiselect__input {
|
||||
@include ghost-input;
|
||||
@include padding($zero);
|
||||
font-size: $font-size-small;
|
||||
|
||||
margin-bottom: $zero;
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
@include padding($space-one);
|
||||
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-labels-wrap {
|
||||
|
||||
&.has-edited,
|
||||
&:hover {
|
||||
.multiselect {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.multiselect>.multiselect__tags {
|
||||
border-color: $color-border;
|
||||
}
|
||||
|
||||
.multiselect>.multiselect__select {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin-top: $space-small;
|
||||
|
||||
>.multiselect__select {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
>.multiselect__tags {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.multiselect--active>.multiselect__tags {
|
||||
border-color: $color-woot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
.code {
|
||||
max-height: $space-mega;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
@include padding($space-one);
|
||||
background: $color-background;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
.integrations-wrap {
|
||||
.integration {
|
||||
background: $color-white;
|
||||
border: 2px solid $color-border;
|
||||
border-radius: $space-slab;
|
||||
padding: $space-normal;
|
||||
|
||||
.integration--image {
|
||||
display: flex;
|
||||
margin-right: $space-normal;
|
||||
width: 8rem;
|
||||
|
||||
img {
|
||||
max-width: 8rem;
|
||||
padding: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
.integration--title {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
|
||||
.integration--description {
|
||||
padding-right: $space-medium;
|
||||
}
|
||||
|
||||
.button-wrap {
|
||||
@include flex;
|
||||
@include flex-align(center, middle);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-wrap {
|
||||
padding-left: $space-large;
|
||||
}
|
||||
@@ -24,9 +24,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
>.icon {
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
&.tiny {
|
||||
font-size: $font-size-mini;
|
||||
padding: $space-small $space-slab;
|
||||
}
|
||||
|
||||
&.round {
|
||||
border-radius: $space-larger;
|
||||
}
|
||||
}
|
||||
|
||||
.button--fixed-right-top {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: middle);
|
||||
@include border-normal-bottom;
|
||||
|
||||
// Resolve Button
|
||||
.button {
|
||||
@include margin(0);
|
||||
@@ -44,6 +45,7 @@
|
||||
.user--name {
|
||||
@include margin(0);
|
||||
font-size: $font-size-medium;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.user--profile__meta {
|
||||
@@ -64,7 +66,7 @@
|
||||
}
|
||||
|
||||
.button.resolve--button {
|
||||
> .icon {
|
||||
>.icon {
|
||||
padding-right: $space-small;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
@include flex;
|
||||
@include flex-shrink;
|
||||
@include padding($space-normal $zero $zero $space-normal);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
background: $color-background;
|
||||
@@ -18,62 +18,63 @@
|
||||
.conversation--user {
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: $zero;
|
||||
text-transform: capitalize;
|
||||
|
||||
.label {
|
||||
position: relative;
|
||||
top: $space-micro;
|
||||
left: $space-micro;
|
||||
max-width: $space-jumbo;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
top: $space-micro;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation--message {
|
||||
height: $space-medium;
|
||||
margin: $zero;
|
||||
font-size: $font-size-small;
|
||||
line-height: $space-medium;
|
||||
font-weight: $font-weight-light;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: $color-body;
|
||||
width: 27rem;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-normal;
|
||||
height: $space-medium;
|
||||
line-height: $space-medium;
|
||||
margin: $zero;
|
||||
max-width: 96%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 27rem;
|
||||
}
|
||||
|
||||
.conversation--meta {
|
||||
@include flex;
|
||||
display: block;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
right: $space-normal;
|
||||
top: $space-normal;
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
|
||||
.unread {
|
||||
$unread-size: $space-two - $space-micro;
|
||||
display: none;
|
||||
height: $unread-size;
|
||||
min-width: $unread-size;
|
||||
background: darken($success-color, 3%);
|
||||
text-align: center;
|
||||
padding: 0 $space-smaller;
|
||||
line-height: $unread-size;
|
||||
color: $color-white;
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: $font-size-mini;
|
||||
margin-left: auto;
|
||||
@include round-corner;
|
||||
background: darken($success-color, 3%);
|
||||
color: $color-white;
|
||||
display: none;
|
||||
font-size: $font-size-mini;
|
||||
font-weight: $font-weight-medium;
|
||||
height: $unread-size;
|
||||
line-height: $unread-size;
|
||||
margin-left: auto;
|
||||
margin-top: $space-smaller;
|
||||
min-width: $unread-size;
|
||||
padding: 0 $space-smaller;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: $font-size-mini;
|
||||
color: $dark-gray;
|
||||
line-height: $space-normal;
|
||||
font-weight: $font-weight-normal;
|
||||
font-size: $font-size-micro;
|
||||
font-weight: $font-weight-normal;
|
||||
line-height: $space-normal;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
@@ -91,4 +92,12 @@
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
|
||||
&.compact {
|
||||
padding-left: 0;
|
||||
|
||||
.conversation--details {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,108 @@
|
||||
@mixin bubble-with-tyes {
|
||||
@include padding($space-smaller $space-one);
|
||||
@include margin($zero);
|
||||
background: $color-primary-light;
|
||||
border-radius: $space-small;
|
||||
color: $color-heading;
|
||||
font-size: $font-size-small;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
bottom: $space-smaller;
|
||||
position: absolute;
|
||||
right: $space-small;
|
||||
}
|
||||
|
||||
.message-text__wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
&::after {
|
||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.audio {
|
||||
.time {
|
||||
margin-top: -$space-two;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
@include flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: -$space-large;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-height: 80%;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
@include padding($space-small);
|
||||
margin-left: -$space-smaller;
|
||||
margin-top: -$space-two;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.locname {
|
||||
font-weight: $font-weight-medium;
|
||||
padding: $space-smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conversations-sidebar {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
|
||||
.load-more-conversations {
|
||||
color: $color-woot;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-small;
|
||||
padding: $space-normal;
|
||||
|
||||
&:hover {
|
||||
background: $color-background;
|
||||
}
|
||||
}
|
||||
|
||||
.end-of-list-text {
|
||||
font-style: italic;
|
||||
padding: $space-normal;
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
@include flex-weight(1);
|
||||
@include scroll-on-hover;
|
||||
}
|
||||
|
||||
.chat-list__top {
|
||||
@include flex;
|
||||
@include padding($space-normal $zero $space-small $zero);
|
||||
@@ -28,10 +129,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
@include flex-weight(1);
|
||||
@include scroll-on-hover;
|
||||
}
|
||||
|
||||
|
||||
.content-box {
|
||||
text-align: center;
|
||||
@@ -47,16 +145,19 @@
|
||||
@include background-gray;
|
||||
@include margin(0);
|
||||
@include border-normal-left;
|
||||
|
||||
.current-chat {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
flex-direction: column;
|
||||
@include flex-align(center, middle);
|
||||
flex-direction: column;
|
||||
|
||||
div {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
flex-direction: column;
|
||||
@include flex-align(center, middle);
|
||||
flex-direction: column;
|
||||
|
||||
img {
|
||||
@include margin($space-normal);
|
||||
width: 10rem;
|
||||
@@ -73,21 +174,22 @@
|
||||
.conv-empty-state {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
flex-direction: column;
|
||||
@include flex-align(center, middle);
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-panel {
|
||||
@include flex;
|
||||
@include flex-weight(1);
|
||||
flex-direction: column;
|
||||
@include margin($zero);
|
||||
flex-direction: column;
|
||||
// Firefox flexbox fix
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
margin-bottom: $space-small;
|
||||
overflow-y: auto;
|
||||
|
||||
> li {
|
||||
>li {
|
||||
@include flex;
|
||||
@include flex-shrink;
|
||||
@include margin($zero $zero $space-smaller);
|
||||
@@ -114,6 +216,7 @@
|
||||
}
|
||||
|
||||
.bubble {
|
||||
@include bubble-with-tyes;
|
||||
max-width: 50rem;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
@@ -147,7 +250,7 @@
|
||||
@include flex-align(right, null);
|
||||
|
||||
.wrap {
|
||||
margin-right: $space-small;
|
||||
margin-right: $space-normal;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -205,6 +308,7 @@
|
||||
@include padding($space-smaller $space-normal);
|
||||
@include flex-align($x: center, $y: null);
|
||||
background: lighten($warning-color, 32%);
|
||||
border: 1px solid lighten($warning-color, 26%);
|
||||
border-radius: $space-smaller;
|
||||
font-size: $font-size-small;
|
||||
|
||||
@@ -237,87 +341,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
@include padding($space-smaller $space-one);
|
||||
@include margin($zero);
|
||||
background: #c7e3ff;
|
||||
border-radius: $space-small;
|
||||
box-shadow: 0 .5px .5px rgba(0, 0, 0, .05);
|
||||
color: $color-heading;
|
||||
font-size: $font-size-small;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
bottom: $space-smaller;
|
||||
position: absolute;
|
||||
right: $space-small;
|
||||
}
|
||||
|
||||
.message-text__wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
&::after {
|
||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.audio {
|
||||
.time {
|
||||
margin-top: -$space-two;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: -$space-large;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-height: 80%;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
@include padding($space-small);
|
||||
margin-left: -$space-smaller;
|
||||
margin-top: -$space-two;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.locname {
|
||||
font-weight: $font-weight-medium;
|
||||
padding: $space-smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
bottom: -$space-micro;
|
||||
color: $color-gray;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
@include padding($space-small);
|
||||
box-sizing: border-box;
|
||||
height: 180px;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
|
||||
.emoji {
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Outside login wrapper
|
||||
.login {
|
||||
@include full-height;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
padding-top: $space-larger * 1.2;
|
||||
|
||||
.login__hero {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
background-color: $color-white;
|
||||
border-radius: $space-small;
|
||||
max-height: 100%;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 60rem;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.reply-box {
|
||||
@include elegant-card;
|
||||
border-bottom: 0;
|
||||
margin: $space-normal;
|
||||
margin-top: 0;
|
||||
border-bottom: 0;
|
||||
@include elegant-card;
|
||||
transition: height 2s $ease-in-out-cubic;
|
||||
max-height: $space-jumbo * 2;
|
||||
transition: height 2s $ease-in-out-cubic;
|
||||
|
||||
.reply-box__top {
|
||||
@include flex;
|
||||
@@ -12,26 +12,25 @@
|
||||
@include padding($space-one $space-normal);
|
||||
@include background-white;
|
||||
@include margin(0);
|
||||
position: relative;
|
||||
border-top-left-radius: $space-small;
|
||||
border-top-right-radius: $space-small;
|
||||
position: relative;
|
||||
|
||||
.canned {
|
||||
@include elegant-card;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
width: 24rem;
|
||||
left: 0;
|
||||
border-top: $space-small solid $color-white;
|
||||
background: $color-white;
|
||||
border-bottom: $space-small solid $color-white;
|
||||
max-height: 14rem;
|
||||
overflow: scroll;
|
||||
border-top: $space-small solid $color-white;
|
||||
left: 0;
|
||||
|
||||
.active {
|
||||
a {
|
||||
background: $color-woot;
|
||||
}
|
||||
max-height: 14rem;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
width: 24rem;
|
||||
z-index: 100;
|
||||
|
||||
.active a {
|
||||
background: $color-woot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,30 +42,30 @@
|
||||
&.is-private {
|
||||
background: lighten($warning-color, 38%);
|
||||
|
||||
> input {
|
||||
>input {
|
||||
background: lighten($warning-color, 38%);
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: $font-size-medium;
|
||||
>.icon {
|
||||
color: $medium-gray;
|
||||
margin-right: $space-small;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-medium;
|
||||
margin-right: $space-small;
|
||||
|
||||
&.active {
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
|
||||
> textarea {
|
||||
>textarea {
|
||||
@include ghost-input();
|
||||
@include margin(0);
|
||||
resize: none;
|
||||
background: transparent;
|
||||
// Override min-height : 50px in foundation
|
||||
//
|
||||
min-height: 1rem;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,48 +79,47 @@
|
||||
|
||||
.tabs {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
|
||||
.tabs-title {
|
||||
margin: 0;
|
||||
transition: background .2s $ease-in-out-cubic;
|
||||
transition: color .2s $ease-in-out-cubic;
|
||||
transition: all .2s $ease-in-out-cubic;
|
||||
transition-property: color, background;
|
||||
|
||||
a {
|
||||
font-weight: $font-weight-medium;
|
||||
padding: $space-one $space-two;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-bottom-left-radius: $space-small;
|
||||
&.is-private.is-active {
|
||||
background: lighten($warning-color, 38%);
|
||||
|
||||
&.is-active {
|
||||
@include border-light-right;
|
||||
border-left: 0;
|
||||
|
||||
a {
|
||||
border-bottom-left-radius: $space-small;
|
||||
}
|
||||
a {
|
||||
border-bottom-color: darken($warning-color, 15%);
|
||||
color: darken($warning-color, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-private {
|
||||
&.is-active {
|
||||
background: lighten($warning-color, 38%);
|
||||
.tabs-title:first-child {
|
||||
border-bottom-left-radius: $space-small;
|
||||
|
||||
a {
|
||||
border-bottom-color: darken($warning-color, 15%);
|
||||
color: darken($warning-color, 15%);
|
||||
}
|
||||
&.is-active {
|
||||
@include border-light-right;
|
||||
border-left: 0;
|
||||
|
||||
a {
|
||||
border-bottom-left-radius: $space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
@include background-white;
|
||||
margin-top: -1px;
|
||||
@include border-light-left;
|
||||
@include border-light-right;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.message-length {
|
||||
@@ -138,11 +136,11 @@
|
||||
}
|
||||
|
||||
.send-button {
|
||||
height: 3.6rem;
|
||||
border-bottom-right-radius: $space-small;
|
||||
padding-top: $space-small;
|
||||
padding-right: $space-two;
|
||||
height: 3.6rem;
|
||||
padding-left: $space-two;
|
||||
padding-right: $space-two;
|
||||
padding-top: $space-small;
|
||||
|
||||
.icon {
|
||||
margin-left: $space-small;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.side-menu {
|
||||
i {
|
||||
min-width: $space-two;
|
||||
margin-right: $space-smaller;
|
||||
min-width: $space-two;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,26 @@
|
||||
border-radius: $space-smaller;
|
||||
color: $color-gray;
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.active a {
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
|
||||
.nested {
|
||||
a {
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: $space-micro;
|
||||
margin-top: $space-micro;
|
||||
|
||||
>.inbox-icon {
|
||||
display: inline-block;
|
||||
margin-right: $space-micro;
|
||||
min-width: $space-normal;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,17 +56,17 @@
|
||||
@include flex;
|
||||
@include space-between-column;
|
||||
@include padding($space-one $space-normal $space-one $space-one);
|
||||
flex-direction: column;
|
||||
@include border-normal-top;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.dropdown-pane {
|
||||
@include elegant-card;
|
||||
@include border-light;
|
||||
display: block;
|
||||
left: 18%;
|
||||
top: -110%;
|
||||
visibility: visible;
|
||||
display: block;
|
||||
width: 80%;
|
||||
z-index: 999;
|
||||
|
||||
@@ -79,23 +99,23 @@
|
||||
font-size: $font-size-medium;
|
||||
margin-top: $space-medium;
|
||||
|
||||
> span {
|
||||
>span {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-title + ul > li > a {
|
||||
.menu-title+ul>li>a {
|
||||
@include padding($space-micro null);
|
||||
color: $medium-gray;
|
||||
line-height: $global-lineheight;
|
||||
}
|
||||
|
||||
.current-user {
|
||||
align-items: center;
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
flex-direction: row;
|
||||
|
||||
.current-user--data {
|
||||
@include flex;
|
||||
@@ -105,7 +125,7 @@
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
line-height: 1;
|
||||
margin-bottom: $zero;
|
||||
margin-bottom: $space-smaller;
|
||||
margin-left: $space-one;
|
||||
margin-top: $space-micro;
|
||||
}
|
||||
@@ -132,7 +152,7 @@
|
||||
display: none;
|
||||
margin-right: $space-normal;
|
||||
|
||||
@media screen and (max-width: 1200px){
|
||||
@media screen and (max-width: 1200px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -141,7 +161,7 @@
|
||||
display: block;
|
||||
margin-right: $space-normal;
|
||||
|
||||
@media screen and (max-width: 1200px){
|
||||
@media screen and (max-width: 1200px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
.tabs-title {
|
||||
a {
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-medium;
|
||||
padding-bottom: $space-slab;
|
||||
padding-top: $space-slab;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ table {
|
||||
tr {
|
||||
.show-if-hover {
|
||||
opacity: 0;
|
||||
transition: all .2s $ease-in-out-cubic;
|
||||
transition: all 0.2s $ease-in-out-cubic;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -3,40 +3,52 @@
|
||||
<div class="chat-list__top">
|
||||
<h1 class="page-title">
|
||||
<woot-sidemenu-icon />
|
||||
{{ inbox.name || pageTitle }}
|
||||
{{ inbox.name || $t('CHAT_LIST.TAB_HEADING') }}
|
||||
</h1>
|
||||
<chat-filter @statusFilterChange="getDataForStatusTab" />
|
||||
<chat-filter @statusFilterChange="updateStatusType" />
|
||||
</div>
|
||||
|
||||
<chat-type-tabs
|
||||
:items="assigneeTabItems"
|
||||
:active-tab-index="activeAssigneeTab"
|
||||
:active-tab="activeAssigneeTab"
|
||||
class="tab--chat-type"
|
||||
@chatTabChange="getDataForTab"
|
||||
@chatTabChange="updateAssigneeTab"
|
||||
/>
|
||||
|
||||
<p
|
||||
v-if="!chatListLoading && !getChatsForTab(activeStatus).length"
|
||||
class="content-box"
|
||||
>
|
||||
<p v-if="!chatListLoading && !getChatsForTab().length" class="content-box">
|
||||
{{ $t('CHAT_LIST.LIST.404') }}
|
||||
</p>
|
||||
|
||||
<div v-if="chatListLoading" class="text-center">
|
||||
<span class="spinner message"></span>
|
||||
</div>
|
||||
|
||||
<transition-group
|
||||
name="conversations-list"
|
||||
tag="div"
|
||||
class="conversations-list"
|
||||
>
|
||||
<div class="conversations-list">
|
||||
<conversation-card
|
||||
v-for="chat in getChatsForTab(activeStatus)"
|
||||
v-for="chat in getChatsForTab()"
|
||||
:key="chat.id"
|
||||
:chat="chat"
|
||||
/>
|
||||
</transition-group>
|
||||
|
||||
<div v-if="chatListLoading" class="text-center">
|
||||
<span class="spinner"></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!hasCurrentPageEndReached && !chatListLoading"
|
||||
class="text-center load-more-conversations"
|
||||
@click="fetchConversations"
|
||||
>
|
||||
{{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }}
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="
|
||||
getChatsForTab().length &&
|
||||
hasCurrentPageEndReached &&
|
||||
!chatListLoading
|
||||
"
|
||||
class="text-center text-muted end-of-list-text"
|
||||
>
|
||||
{{ $t('CHAT_LIST.EOF') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,11 +71,11 @@ export default {
|
||||
ChatFilter,
|
||||
},
|
||||
mixins: [timeMixin, conversationMixin],
|
||||
props: ['conversationInbox', 'pageTitle'],
|
||||
props: ['conversationInbox'],
|
||||
data() {
|
||||
return {
|
||||
activeAssigneeTab: 0,
|
||||
activeStatus: 0,
|
||||
activeAssigneeTab: wootConstants.ASSIGNEE_TYPE.ME,
|
||||
activeStatus: wootConstants.STATUS_TYPE.OPEN,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -78,66 +90,69 @@ export default {
|
||||
convStats: 'getConvTabStats',
|
||||
}),
|
||||
assigneeTabItems() {
|
||||
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map((item, index) => ({
|
||||
id: index,
|
||||
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => ({
|
||||
key: item.KEY,
|
||||
name: item.NAME,
|
||||
count: this.convStats[item.KEY] || 0,
|
||||
count: this.convStats[item.COUNT_KEY] || 0,
|
||||
}));
|
||||
},
|
||||
inbox() {
|
||||
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
|
||||
},
|
||||
getToggleStatus() {
|
||||
if (this.toggleType) {
|
||||
return 'Open';
|
||||
}
|
||||
return 'Resolved';
|
||||
currentPage() {
|
||||
return this.$store.getters['conversationPage/getCurrentPage'](
|
||||
this.activeAssigneeTab
|
||||
);
|
||||
},
|
||||
hasCurrentPageEndReached() {
|
||||
return this.$store.getters['conversationPage/getHasEndReached'](
|
||||
this.activeAssigneeTab
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
conversationInbox() {
|
||||
this.resetAndFetchData();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$watch('$store.state.route', () => {
|
||||
if (this.$store.state.route.name !== 'inbox_conversation') {
|
||||
this.$store.dispatch('emptyAllConversations');
|
||||
this.fetchData();
|
||||
}
|
||||
});
|
||||
|
||||
this.$store.dispatch('emptyAllConversations');
|
||||
this.fetchData();
|
||||
this.$store.dispatch('setChatFilter', this.activeStatus);
|
||||
this.resetAndFetchData();
|
||||
this.$store.dispatch('agents/get');
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
if (this.chatLists.length === 0) {
|
||||
this.fetchConversations();
|
||||
}
|
||||
resetAndFetchData() {
|
||||
this.$store.dispatch('conversationPage/reset');
|
||||
this.$store.dispatch('emptyAllConversations');
|
||||
this.fetchConversations();
|
||||
},
|
||||
fetchConversations() {
|
||||
this.$store.dispatch('fetchAllConversations', {
|
||||
inboxId: this.conversationInbox ? this.conversationInbox : undefined,
|
||||
assigneeType: this.activeAssigneeTab,
|
||||
status: this.activeStatus ? 'resolved' : 'open',
|
||||
status: this.activeStatus,
|
||||
page: this.currentPage + 1,
|
||||
});
|
||||
},
|
||||
getDataForTab(index) {
|
||||
if (this.activeAssigneeTab !== index) {
|
||||
this.activeAssigneeTab = index;
|
||||
this.fetchConversations();
|
||||
updateAssigneeTab(selectedTab) {
|
||||
if (this.activeAssigneeTab !== selectedTab) {
|
||||
this.activeAssigneeTab = selectedTab;
|
||||
if (!this.currentPage) {
|
||||
this.fetchConversations();
|
||||
}
|
||||
}
|
||||
},
|
||||
getDataForStatusTab(index) {
|
||||
updateStatusType(index) {
|
||||
if (this.activeStatus !== index) {
|
||||
this.activeStatus = index;
|
||||
this.fetchConversations();
|
||||
this.resetAndFetchData();
|
||||
}
|
||||
},
|
||||
getChatsForTab() {
|
||||
let copyList = [];
|
||||
if (this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE_SLUG.MINE) {
|
||||
if (this.activeAssigneeTab === 'me') {
|
||||
copyList = this.mineChatsList.slice();
|
||||
} else if (
|
||||
this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE_SLUG.UNASSIGNED
|
||||
) {
|
||||
} else if (this.activeAssigneeTab === 'unassigned') {
|
||||
copyList = this.unAssignedChatsList.slice();
|
||||
} else {
|
||||
copyList = this.allChatList.slice();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<transition name="modal-fade">
|
||||
<div v-if="show" class="modal-mask" transition="modal" @click="close">
|
||||
<div
|
||||
v-if="show"
|
||||
class="modal-mask"
|
||||
transition="modal"
|
||||
@click="onBackDropClick"
|
||||
>
|
||||
<i class="ion-android-close modal--close" @click="close"></i>
|
||||
<div class="modal-container" :class="className" @click.stop>
|
||||
<slot />
|
||||
@@ -12,9 +17,19 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
closeOnBackdropClick: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
show: Boolean,
|
||||
onClose: Function,
|
||||
className: String,
|
||||
onClose: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', e => {
|
||||
@@ -27,6 +42,11 @@ export default {
|
||||
close() {
|
||||
this.onClose();
|
||||
},
|
||||
onBackDropClick() {
|
||||
if (this.closeOnBackdropClick) {
|
||||
this.onClose();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<span class="spinner small"></span>
|
||||
</template>
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from '../Spinner';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
import Spinner from '../Spinner';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import wootConstants from '../../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
props: ['conversationId'],
|
||||
data() {
|
||||
return {
|
||||
@@ -29,19 +33,23 @@ export default {
|
||||
currentChat: 'getSelectedChat',
|
||||
}),
|
||||
currentStatus() {
|
||||
const ButtonName = this.currentChat.status === 0 ? 'Resolve' : 'Reopen';
|
||||
const ButtonName =
|
||||
this.currentChat.status === wootConstants.STATUS_TYPE.OPEN
|
||||
? this.$t('CONVERSATION.HEADER.RESOLVE_ACTION')
|
||||
: this.$t('CONVERSATION.HEADER.REOPEN_ACTION');
|
||||
return ButtonName;
|
||||
},
|
||||
buttonClass() {
|
||||
return this.currentChat.status === 0 ? 'success' : 'warning';
|
||||
return this.currentChat.status === wootConstants.STATUS_TYPE.OPEN
|
||||
? 'success'
|
||||
: 'warning';
|
||||
},
|
||||
buttonIconClass() {
|
||||
return this.currentChat.status === 0 ? 'ion-checkmark' : 'ion-refresh';
|
||||
return this.currentChat.status === wootConstants.STATUS_TYPE.OPEN
|
||||
? 'ion-checkmark'
|
||||
: 'ion-refresh';
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
methods: {
|
||||
toggleStatus() {
|
||||
this.isLoading = true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint no-plusplus: 0 */
|
||||
/* eslint-env browser */
|
||||
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import Bar from './widgets/chart/BarChart';
|
||||
import Code from './Code';
|
||||
import LoadingState from './widgets/LoadingState';
|
||||
@@ -8,7 +9,6 @@ import Modal from './Modal';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ReportStatsCard from './widgets/ReportStatsCard';
|
||||
import SidemenuIcon from './SidemenuIcon';
|
||||
import Spinner from './Spinner';
|
||||
import SubmitButton from './buttons/FormSubmitButton';
|
||||
import Tabs from './ui/Tabs/Tabs';
|
||||
import TabsItem from './ui/Tabs/TabsItem';
|
||||
|
||||
@@ -42,12 +42,21 @@
|
||||
class="dropdown-pane top"
|
||||
>
|
||||
<ul class="vertical dropdown menu">
|
||||
<li><a href="#" @click.prevent="logout()">Logout</a></li>
|
||||
<li>
|
||||
<router-link to="/app/profile/settings">
|
||||
{{ $t('SIDEBAR.PROFILE_SETTINGS') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click.prevent="logout()">
|
||||
{{ $t('SIDEBAR.LOGOUT') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
<div class="current-user" @click.prevent="showOptions()">
|
||||
<thumbnail :src="gravatarUrl()" :username="currentUser.name" />
|
||||
<thumbnail :src="currentUser.avatar_url" :username="currentUser.name" />
|
||||
<div class="current-user--data">
|
||||
<h3 class="current-user--name">
|
||||
{{ currentUser.name }}
|
||||
@@ -65,7 +74,6 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import md5 from 'md5';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import adminMixin from '../../mixins/isAdmin';
|
||||
@@ -99,6 +107,7 @@ export default {
|
||||
daysLeft: 'getTrialLeft',
|
||||
subscriptionData: 'getSubscription',
|
||||
inboxes: 'inboxes/getInboxes',
|
||||
currentUser: 'getCurrentUser',
|
||||
}),
|
||||
accessibleMenuItems() {
|
||||
// get all keys in menuGroup
|
||||
@@ -141,12 +150,10 @@ export default {
|
||||
id: inbox.id,
|
||||
label: inbox.name,
|
||||
toState: frontendURL(`inbox/${inbox.id}`),
|
||||
type: inbox.channel_type,
|
||||
})),
|
||||
};
|
||||
},
|
||||
currentUser() {
|
||||
return Auth.getCurrentUser();
|
||||
},
|
||||
dashboardPath() {
|
||||
return frontendURL('dashboard');
|
||||
},
|
||||
@@ -174,10 +181,6 @@ export default {
|
||||
this.$store.dispatch('inboxes/get');
|
||||
},
|
||||
methods: {
|
||||
gravatarUrl() {
|
||||
const hash = md5(this.currentUser.email);
|
||||
return `${window.WootConstants.GRAVATAR_URL}${hash}?default=404`;
|
||||
},
|
||||
filterBillingRoutes(menuItems) {
|
||||
return menuItems.filter(
|
||||
menuItem => !menuItem.toState.includes('billing')
|
||||
@@ -185,6 +188,9 @@ export default {
|
||||
},
|
||||
filterMenuItemsByRole(menuItems) {
|
||||
const { role } = this.currentUser;
|
||||
if (!role) {
|
||||
return [];
|
||||
}
|
||||
return menuItems.filter(
|
||||
menuItem =>
|
||||
window.roleWiseRoutes[role].indexOf(menuItem.toStateName) > -1
|
||||
|
||||
@@ -24,11 +24,17 @@
|
||||
v-for="child in menuItem.children"
|
||||
:key="child.id"
|
||||
active-class="active flex-container"
|
||||
:class="computedInboxClass(child)"
|
||||
tag="li"
|
||||
:to="child.toState"
|
||||
>
|
||||
<a>{{ child.label }}</a>
|
||||
<a href="#">
|
||||
<i
|
||||
v-if="computedInboxClass(child)"
|
||||
class="inbox-icon"
|
||||
:class="computedInboxClass(child)"
|
||||
></i>
|
||||
{{ child.label }}
|
||||
</a>
|
||||
</router-link>
|
||||
</ul>
|
||||
</router-link>
|
||||
@@ -41,6 +47,27 @@ import { mapGetters } from 'vuex';
|
||||
import router from '../../routes';
|
||||
import auth from '../../api/auth';
|
||||
|
||||
const INBOX_TYPES = {
|
||||
WEB: 'Channel::WebWidget',
|
||||
FB: 'Channel::FacebookPage',
|
||||
TWITTER: 'Channel::TwitterProfile',
|
||||
};
|
||||
const getInboxClassByType = type => {
|
||||
switch (type) {
|
||||
case INBOX_TYPES.WEB:
|
||||
return 'ion-earth';
|
||||
|
||||
case INBOX_TYPES.FB:
|
||||
return 'ion-social-facebook';
|
||||
|
||||
case INBOX_TYPES.TWITTER:
|
||||
return 'ion-social-twitter';
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
menuItem: {
|
||||
@@ -75,10 +102,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
computedInboxClass(child) {
|
||||
if (parseInt(this.activeInbox, 10) === child.channel_id) {
|
||||
return 'active flex-container';
|
||||
}
|
||||
return ' ';
|
||||
const { type } = child;
|
||||
const classByType = getInboxClassByType(type);
|
||||
return classByType;
|
||||
},
|
||||
newLinkClick() {
|
||||
router.push({ name: 'settings_inbox_new', params: { page: 'new' } });
|
||||
|
||||
@@ -14,15 +14,19 @@ export default {
|
||||
props: {
|
||||
username: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'white',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1f93ff',
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
@@ -30,6 +34,7 @@ export default {
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
@@ -72,7 +77,7 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.avatar-container {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
isActive(channel) {
|
||||
return ['facebook', 'website'].includes(channel);
|
||||
return ['facebook', 'website', 'twitter'].includes(channel);
|
||||
},
|
||||
onItemClick() {
|
||||
if (this.isActive(this.channel)) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<woot-tabs :index="tabsIndex" @change="onTabChange">
|
||||
<woot-tabs :index="activeTabIndex" @change="onTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="item in items"
|
||||
:key="item.name"
|
||||
:key="item.key"
|
||||
:name="item.name"
|
||||
:count="item.count"
|
||||
/>
|
||||
</woot-tabs>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
import wootConstants from '../../constants';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -17,24 +17,25 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
activeTabIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: wootConstants.ASSIGNEE_TYPE.ME,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabsIndex: 0,
|
||||
tabsIndex: wootConstants.ASSIGNEE_TYPE.ME,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.tabsIndex = this.activeTabIndex;
|
||||
computed: {
|
||||
activeTabIndex() {
|
||||
return this.items.findIndex(item => item.key === this.activeTab);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onTabChange(selectedTabIndex) {
|
||||
if (selectedTabIndex !== this.tabsIndex) {
|
||||
this.$emit('chatTabChange', selectedTabIndex);
|
||||
this.tabsIndex = selectedTabIndex;
|
||||
if (this.items[selectedTabIndex].key !== this.activeTab) {
|
||||
this.$emit('chatTabChange', this.items[selectedTabIndex].key);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
v-if="!imgError && Boolean(src)"
|
||||
id="image"
|
||||
:src="src"
|
||||
class="user-thumbnail"
|
||||
:class="thumbnailClass"
|
||||
@error="onImgError()"
|
||||
/>
|
||||
<Avatar
|
||||
v-else
|
||||
:username="username"
|
||||
class="user-thumbnail"
|
||||
:class="thumbnailClass"
|
||||
background-color="#1f93ff"
|
||||
color="white"
|
||||
:size="avatarSize"
|
||||
@@ -22,6 +22,18 @@
|
||||
:style="badgeStyle"
|
||||
src="~dashboard/assets/images/fb-badge.png"
|
||||
/>
|
||||
<div
|
||||
v-else-if="status === 'online'"
|
||||
class="source-badge user--online"
|
||||
:style="statusStyle"
|
||||
></div>
|
||||
<img
|
||||
v-if="badge === 'Channel::TwitterProfile'"
|
||||
id="badge"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
src="~dashboard/assets/images/twitter-badge.png"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -41,6 +53,7 @@ export default {
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
@@ -52,6 +65,15 @@ export default {
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hasBorder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -67,6 +89,14 @@ export default {
|
||||
const badgeSize = `${this.avatarSize / 3}px`;
|
||||
return { width: badgeSize, height: badgeSize };
|
||||
},
|
||||
statusStyle() {
|
||||
const statusSize = `${this.avatarSize / 4}px`;
|
||||
return { width: statusSize, height: statusSize };
|
||||
},
|
||||
thumbnailClass() {
|
||||
const classname = this.hasBorder ? 'border' : '';
|
||||
return `user-thumbnail ${classname}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onImgError() {
|
||||
@@ -78,6 +108,7 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
@import '~dashboard/assets/scss/foundation-settings';
|
||||
@import '~dashboard/assets/scss/mixins';
|
||||
|
||||
.user-thumbnail-box {
|
||||
@@ -88,14 +119,29 @@ export default {
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.border {
|
||||
border: 1px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
.source-badge {
|
||||
bottom: -$space-micro / 2;
|
||||
bottom: -$space-micro;
|
||||
height: $space-slab;
|
||||
position: absolute;
|
||||
right: $zero;
|
||||
width: $space-slab;
|
||||
}
|
||||
|
||||
.user--online {
|
||||
background: $success-color;
|
||||
border-radius: 50%;
|
||||
bottom: $space-micro;
|
||||
|
||||
&:after {
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<select v-model="activeIndex" class="status--filter" @change="onTabChange()">
|
||||
<select v-model="activeStatus" class="status--filter" @change="onTabChange()">
|
||||
<option
|
||||
v-for="item in $t('CHAT_LIST.CHAT_STATUS_ITEMS')"
|
||||
:key="item['VALUE']"
|
||||
@@ -11,15 +11,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import wootConstants from '../../../constants';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
activeIndex: 0,
|
||||
activeStatus: wootConstants.STATUS_TYPE.OPEN,
|
||||
}),
|
||||
mounted() {},
|
||||
methods: {
|
||||
onTabChange() {
|
||||
this.$store.dispatch('setChatFilter', this.activeIndex);
|
||||
this.$emit('statusFilterChange', this.activeIndex);
|
||||
this.$store.dispatch('setChatFilter', this.activeStatus);
|
||||
this.$emit('statusFilterChange', this.activeStatus);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@click="cardClick(chat)"
|
||||
>
|
||||
<Thumbnail
|
||||
v-if="!hideThumbnail"
|
||||
:src="chat.meta.sender.thumbnail"
|
||||
:badge="chat.meta.sender.channel"
|
||||
class="columns"
|
||||
@@ -15,7 +16,7 @@
|
||||
<h4 class="conversation--user">
|
||||
{{ chat.meta.sender.name }}
|
||||
<span
|
||||
v-if="isInboxNameVisible"
|
||||
v-if="!hideInboxName && isInboxNameVisible"
|
||||
v-tooltip.bottom="inboxName(chat.inbox_id)"
|
||||
class="label"
|
||||
>
|
||||
@@ -45,7 +46,7 @@ import getEmojiSVG from '../emoji/utils';
|
||||
import conversationMixin from '../../../mixins/conversations';
|
||||
import timeMixin from '../../../mixins/time';
|
||||
import router from '../../../routes';
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -58,6 +59,14 @@ export default {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
hideInboxName: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hideThumbnail: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -86,9 +95,9 @@ export default {
|
||||
|
||||
methods: {
|
||||
cardClick(chat) {
|
||||
router.push({
|
||||
path: frontendURL(`conversations/${chat.id}`),
|
||||
});
|
||||
const { activeInbox } = this;
|
||||
const path = conversationUrl(activeInbox, chat.id);
|
||||
router.push({ path: frontendURL(path) });
|
||||
},
|
||||
extractMessageText(chatItem) {
|
||||
if (chatItem.content) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user