Merge branch 'release/3.0.0'

This commit is contained in:
Sojan
2023-08-15 22:41:04 -07:00
1885 changed files with 54504 additions and 26108 deletions

View File

@@ -12,7 +12,7 @@ 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: cimg/postgres:14.1
- image: cimg/postgres:15.3
- image: cimg/redis:6.2.6
environment:
- RAILS_LOG_TO_STDOUT: false
@@ -73,7 +73,7 @@ jobs:
- run:
name: yarn
command: yarn install --cache-folder ~/.cache/yarn
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
# Store yarn / webpacker cache
- save_cache:
@@ -104,9 +104,8 @@ jobs:
fi
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
# Database setup
- run: yarn install --check-files
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
@@ -117,7 +116,7 @@ jobs:
- run:
name: Rubocop
command: bundle exec rubocop
# - run:
# name: Brakeman
# command: bundle exec brakeman
@@ -126,6 +125,21 @@ jobs:
name: eslint
command: yarn run eslint
# Run frontend tests
- run:
name: Run frontend tests
command: |
mkdir -p ~/tmp/test-results/frontend_specs
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage
command: |
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
# Run rails tests
- run:
name: Run backend tests
@@ -145,20 +159,6 @@ jobs:
command: |
~/tmp/cc-test-reporter format-coverage -t simplecov -o "coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
- run:
name: Run frontend tests
command: |
mkdir -p ~/tmp/test-results/frontend_specs
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage
command: |
~/tmp/cc-test-reporter format-coverage -t lcov -o "coverage/codeclimate.frontend_$CIRCLE_NODE_INDEX.json"
- persist_to_workspace:
root: coverage
paths:

View File

@@ -230,3 +230,11 @@ AZURE_APP_SECRET=
## Change these values to fine tune performance
# control the concurrency setting of sidekiq
# SIDEKIQ_CONCURRENCY=10
# AI powered features
## OpenAI key
# OPENAI_API_KEY=
# Sentiment analysis model file path
SENTIMENT_FILE_PATH=

23
.github/workflows/lint_pr.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
# ref: https://github.com/amannn/action-semantic-pull-request
# ensure PR title is in semantic format
name: "Lint PR"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
permissions:
pull-requests: read
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,55 @@
name: Log Lines Percentage Check
on:
pull_request:
branches:
- develop
jobs:
log_lines_check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Check for log lines and calculate percentage
run: |
# Define the log line pattern
LOG_LINE_PATTERN="Rails\.logger"
# Get the list of changed files in the pull request
CHANGED_FILES=$(git diff --name-only)
# Initialize a flag to track if any files have insufficient log lines
INSUFFICIENT_LOGS=0
for file in $CHANGED_FILES; do
if [[ $file =~ \.rb$ && ! $file =~ _spec\.rb$ ]]; then
# Count the total number of lines in the file
total_lines=$(wc -l < "$file")
# Count the number of log lines in the file
log_lines=$(grep -c "$LOG_LINE_PATTERN" "$file")
# Calculate the percentage of log lines
if [ "$total_lines" -gt 0 ]; then
percentage=$(awk "BEGIN { pc=100*${log_lines}/${total_lines}; i=int(pc); print (pc-i<0.5)?i:i+1 }")
else
percentage=0
fi
# Check if the percentage is less than 5%
if [ "$percentage" -lt 5 ]; then
echo "Error: Log lines percentage is less than 5% ($percentage%) in $file. Please add more log lines using Rails.logger statements."
INSUFFICIENT_LOGS=1
else
echo "Log lines percentage is $percentage% in $file. Code looks good!"
fi
fi
done
# If any files have insufficient log lines, fail the action
if [ "$INSUFFICIENT_LOGS" -eq 1 ]; then
exit 1
fi

View File

@@ -18,11 +18,12 @@ jobs:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10.8
image: postgres:15.3
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ""
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck

View File

@@ -0,0 +1,78 @@
# #
# # This workflow will run specs related to response bot
# # This can only be activated in installations Where vector extension is available.
# #
name: Run Response Bot spec
on:
push:
branches:
- develop
- master
pull_request:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-20.04
services:
postgres:
image: ankane/pgvector
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ""
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck
# tmpfs makes DB faster by using RAM
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: yarn
run: yarn install
- name: Create database
run: bundle exec rake db:create
- name: Seed database
run: bundle exec rake db:schema:load
- name: Enable ResponseBotService in installation
run: RAILS_ENV=test bundle exec rails runner "Features::ResponseBotService.new.enable_in_installation"
# Run Response Bot specs
- name: Run backend tests
run: |
bundle exec rspec spec/enterprise/controllers/api/v1/accounts/response_sources_controller_spec.rb spec/enterprise/controllers/enterprise/api/v1/accounts/inboxes_controller_spec.rb:47 --profile=10 --format documentation
- name: Upload rails log folder
uses: actions/upload-artifact@v3
if: always()
with:
name: rails-log-folder
path: log

2
.gitignore vendored
View File

@@ -74,3 +74,5 @@ yalc.lock
/yarn-error.log
yarn-debug.log*
.yarn-integrity
/storybook-static

2
.nvmrc
View File

@@ -1 +1 @@
14.17.4
16.20.1

View File

@@ -87,6 +87,7 @@ Style/ClassAndModuleChildren:
EnforcedStyle: compact
Exclude:
- 'config/application.rb'
- 'config/initializers/monkey_patches/*'
Style/MapToHash:
Enabled: false
Style/HashSyntax:

View File

@@ -23,6 +23,18 @@ module.exports = {
},
'@storybook/addon-links',
'@storybook/addon-essentials',
{
/**
* Fix Storybook issue with PostCSS@8
* @see https://github.com/storybookjs/storybook/issues/12668#issuecomment-773958085
*/
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
webpackFinal: config => {
const newConfig = {
@@ -35,7 +47,7 @@ module.exports = {
newConfig.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
include: path.resolve(__dirname, '../app/javascript'),
});

View File

@@ -4,10 +4,12 @@ import Vuex from 'vuex';
import VueI18n from 'vue-i18n';
import Vuelidate from 'vuelidate';
import Multiselect from 'vue-multiselect';
import VueDOMPurifyHTML from 'vue-dompurify-html';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon';
import WootUiKit from '../app/javascript/dashboard/components';
import i18n from '../app/javascript/dashboard/i18n';
import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer';
import '../app/javascript/dashboard/assets/scss/storybook.scss';
@@ -15,6 +17,8 @@ Vue.use(VueI18n);
Vue.use(Vuelidate);
Vue.use(WootUiKit);
Vue.use(Vuex);
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
Vue.component('multiselect', Multiselect);
Vue.component('fluent-icon', FluentIcon);

25
Gemfile
View File

@@ -4,7 +4,7 @@ ruby '3.2.2'
##-- base gems for rails --##
gem 'rack-cors', require: 'rack/cors'
gem 'rails', '~> 7'
gem 'rails', '~> 7.0.5.1'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false
@@ -34,7 +34,7 @@ gem 'commonmarker'
# Validate Data against JSON Schema
gem 'json_schemer'
# Rack middleware for blocking & throttling abusive requests
gem 'rack-attack'
gem 'rack-attack', '>= 6.7.0'
# a utility tool for streaming, flexible and safe downloading of remote files
gem 'down'
# authentication type to fetch and send mail over oauth2.0
@@ -74,7 +74,7 @@ gem 'devise_token_auth'
gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate'
gem 'administrate', '>= 0.19.0'
gem 'administrate-field-active_storage'
##--- gems for pubsub service ---##
@@ -108,9 +108,9 @@ gem 'elastic-apm', require: false
gem 'newrelic_rpm', require: false
gem 'newrelic-sidekiq-metrics', require: false
gem 'scout_apm', require: false
gem 'sentry-rails', require: false
gem 'sentry-rails', '>= 5.10.0', require: false
gem 'sentry-ruby', require: false
gem 'sentry-sidekiq', require: false
gem 'sentry-sidekiq', '>= 5.10.0', require: false
##-- background job processing --##
gem 'sidekiq'
@@ -153,7 +153,7 @@ gem 'stripe'
gem 'faker'
# Include logrange conditionally in intializer using env variable
gem 'lograge', '~> 0.12.0', require: false
gem 'lograge', '~> 0.13.0', require: false
# worked with microsoft refresh token
gem 'omniauth-oauth2'
@@ -165,6 +165,16 @@ gem 'omniauth'
gem 'omniauth-google-oauth2'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
## Gems for reponse bot
# adds cosine similarity to postgres using vector extension
gem 'neighbor'
gem 'pgvector'
# Convert Website HTML to Markdown
gem 'reverse_markdown'
# Sentiment analysis
gem 'informers'
### Gems required only in specific deployment environments ###
##############################################################
@@ -187,7 +197,7 @@ group :development do
gem 'squasher'
# profiling
gem 'rack-mini-profiler', require: false
gem 'rack-mini-profiler', '>= 3.1.1', require: false
gem 'stackprof'
end
@@ -210,6 +220,7 @@ group :development, :test do
gem 'bundle-audit', require: false
gem 'byebug', platform: :mri
gem 'climate_control'
gem 'debug', '~> 1.8'
gem 'factory_bot_rails'
gem 'listen'
gem 'mock_redis'

View File

@@ -33,70 +33,70 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
actioncable (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
actionmailbox (7.0.5.1)
actionpack (= 7.0.5.1)
activejob (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.5)
actionpack (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activesupport (= 7.0.5)
actionmailer (7.0.5.1)
actionpack (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activesupport (= 7.0.5.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.5)
actionview (= 7.0.5)
activesupport (= 7.0.5)
actionpack (7.0.5.1)
actionview (= 7.0.5.1)
activesupport (= 7.0.5.1)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.5)
actionpack (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
actiontext (7.0.5.1)
actionpack (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.5)
activesupport (= 7.0.5)
actionview (7.0.5.1)
activesupport (= 7.0.5.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_record_query_trace (1.8)
activejob (7.0.5)
activesupport (= 7.0.5)
activejob (7.0.5.1)
activesupport (= 7.0.5.1)
globalid (>= 0.3.6)
activemodel (7.0.5)
activesupport (= 7.0.5)
activerecord (7.0.5)
activemodel (= 7.0.5)
activesupport (= 7.0.5)
activemodel (7.0.5.1)
activesupport (= 7.0.5.1)
activerecord (7.0.5.1)
activemodel (= 7.0.5.1)
activesupport (= 7.0.5.1)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activesupport (= 7.0.5)
activestorage (7.0.5.1)
actionpack (= 7.0.5.1)
activejob (= 7.0.5.1)
activerecord (= 7.0.5.1)
activesupport (= 7.0.5.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.5)
activesupport (7.0.5.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -105,7 +105,7 @@ GEM
activerecord (>= 6.0, < 7.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
administrate (0.18.0)
administrate (0.19.0)
actionpack (>= 5.0)
actionview (>= 5.0)
activerecord (>= 5.0)
@@ -145,6 +145,7 @@ GEM
statsd-ruby (~> 1.1)
bcrypt (3.1.18)
bindex (0.8.1)
blingfire (0.1.8)
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (5.4.1)
@@ -161,9 +162,9 @@ GEM
byebug (11.1.3)
climate_control (1.2.0)
coderay (1.1.3)
commonmarker (0.23.9)
commonmarker (0.23.10)
concurrent-ruby (1.2.2)
connection_pool (2.4.0)
connection_pool (2.4.1)
crack (0.4.5)
rexml
crass (1.0.6)
@@ -183,6 +184,9 @@ GEM
libddwaf (~> 1.8.2.0.0)
msgpack
debase-ruby_core_source (3.2.0)
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
declarative (0.0.20)
devise (4.9.2)
bcrypt (~> 3.0)
@@ -361,11 +365,18 @@ GEM
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
informers (0.2.0)
blingfire (>= 0.1.7)
numo-narray
onnxruntime (>= 0.5.1)
io-console (0.6.0)
irb (1.7.2)
reline (>= 0.3.6)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jmespath (1.6.2)
jquery-rails (4.5.1)
jquery-rails (4.6.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
@@ -418,7 +429,7 @@ GEM
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
lograge (0.12.0)
lograge (0.13.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
@@ -440,17 +451,19 @@ GEM
mime-types-data (3.2023.0218.1)
mini_magick (4.12.0)
mini_mime (1.1.2)
mini_portile2 (2.8.2)
minitest (5.18.0)
mini_portile2 (2.8.4)
minitest (5.19.0)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.3.0)
neighbor (0.2.3)
activerecord (>= 5.2)
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.3.4)
net-imap (0.3.6)
date
net-protocol
net-pop (0.1.2)
@@ -465,15 +478,16 @@ GEM
sidekiq
newrelic_rpm (8.16.0)
nio4r (2.5.9)
nokogiri (1.15.2)
nokogiri (1.15.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.15.2-arm64-darwin)
nokogiri (1.15.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.2-x86_64-darwin)
nokogiri (1.15.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.15.2-x86_64-linux)
nokogiri (1.15.3-x86_64-linux)
racc (~> 1.4)
numo-narray (0.9.2.1)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
snaky_hash (~> 2.0)
@@ -502,6 +516,14 @@ GEM
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
onnxruntime (0.7.6)
ffi
onnxruntime (0.7.6-arm64-darwin)
ffi
onnxruntime (0.7.6-x86_64-darwin)
ffi
onnxruntime (0.7.6-x86_64-linux)
ffi
openssl (3.1.0)
orm_adapter (0.5.0)
os (1.1.4)
@@ -512,6 +534,7 @@ GEM
pg_search (2.3.6)
activerecord (>= 5.2)
activesupport (>= 5.2)
pgvector (0.1.1)
procore-sift (1.0.0)
activerecord (>= 6.1)
pry (0.14.2)
@@ -525,13 +548,13 @@ GEM
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.0)
rack (2.2.7)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
racc (1.7.1)
rack (2.2.8)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.1)
rack (>= 2.0.0)
rack-mini-profiler (3.1.0)
rack-mini-profiler (3.1.1)
rack (>= 1.2.0)
rack-protection (3.0.6)
rack
@@ -540,29 +563,30 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.5)
actioncable (= 7.0.5)
actionmailbox (= 7.0.5)
actionmailer (= 7.0.5)
actionpack (= 7.0.5)
actiontext (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activemodel (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
rails (7.0.5.1)
actioncable (= 7.0.5.1)
actionmailbox (= 7.0.5.1)
actionmailer (= 7.0.5.1)
actionpack (= 7.0.5.1)
actiontext (= 7.0.5.1)
actionview (= 7.0.5.1)
activejob (= 7.0.5.1)
activemodel (= 7.0.5.1)
activerecord (= 7.0.5.1)
activestorage (= 7.0.5.1)
activesupport (= 7.0.5.1)
bundler (>= 1.15.0)
railties (= 7.0.5)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
railties (= 7.0.5.1)
rails-dom-testing (2.1.1)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
railties (7.0.5.1)
actionpack (= 7.0.5.1)
activesupport (= 7.0.5.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -579,6 +603,8 @@ GEM
redis-namespace (1.10.0)
redis (>= 4)
regexp_parser (2.8.0)
reline (0.3.6)
io-console (~> 0.5)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -594,6 +620,8 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retriable (3.1.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.5)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
@@ -669,18 +697,18 @@ GEM
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.9.0)
sentry-rails (5.10.0)
railties (>= 5.0)
sentry-ruby (~> 5.9.0)
sentry-ruby (5.9.0)
sentry-ruby (~> 5.10.0)
sentry-ruby (5.10.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-sidekiq (5.9.0)
sentry-ruby (~> 5.9.0)
sentry-sidekiq (5.10.0)
sentry-ruby (~> 5.10.0)
sidekiq (>= 3.0)
sexp_processor (4.17.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (7.1.0)
sidekiq (7.1.2)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
@@ -731,7 +759,7 @@ GEM
time_diff (0.3.0)
activesupport
i18n
timeout (0.3.2)
timeout (0.4.0)
trailblazer-option (0.1.2)
twilio-ruby (5.77.0)
faraday (>= 0.9, < 3.0)
@@ -784,7 +812,7 @@ GEM
working_hours (1.4.1)
activesupport (>= 3.2)
tzinfo
zeitwerk (2.6.8)
zeitwerk (2.6.9)
PLATFORMS
arm64-darwin-20
@@ -801,7 +829,7 @@ DEPENDENCIES
active_record_query_trace
activerecord-import
acts-as-taggable-on
administrate
administrate (>= 0.19.0)
administrate-field-active_storage
annotate
attr_extras
@@ -821,6 +849,7 @@ DEPENDENCIES
cypress-on-rails
database_cleaner
ddtrace
debug (~> 1.8)
devise
devise-secure_password!
devise_token_auth
@@ -846,6 +875,7 @@ DEPENDENCIES
hashie
html2text!
image_processing
informers
jbuilder
json_refs
json_schemer
@@ -856,9 +886,10 @@ DEPENDENCIES
line-bot-api
liquid
listen
lograge (~> 0.12.0)
lograge (~> 0.13.0)
maxminddb
mock_redis
neighbor
newrelic-sidekiq-metrics
newrelic_rpm
omniauth
@@ -867,19 +898,21 @@ DEPENDENCIES
omniauth-rails_csrf_protection (~> 1.0)
pg
pg_search
pgvector
procore-sift
pry-rails
puma
pundit
rack-attack
rack-attack (>= 6.7.0)
rack-cors
rack-mini-profiler
rack-mini-profiler (>= 3.1.1)
rack-timeout
rails (~> 7)
rails (~> 7.0.5.1)
redis
redis-namespace
responders
rest-client
reverse_markdown
rspec-rails
rspec_junit_formatter
rubocop
@@ -889,9 +922,9 @@ DEPENDENCIES
scout_apm
scss_lint
seed_dump
sentry-rails
sentry-rails (>= 5.10.0)
sentry-ruby
sentry-sidekiq
sentry-sidekiq (>= 5.10.0)
shoulda-matchers
sidekiq
sidekiq-cron

View File

@@ -30,9 +30,23 @@ burn:
bundle && yarn
run:
@if [ -f ./.overmind.sock ]; then \
echo "Overmind is already running. Use 'make force_run' to start a new instance."; \
else \
overmind start -f Procfile.dev; \
fi
force_run:
rm -f ./.overmind.sock
overmind start -f Procfile.dev
debug:
overmind connect backend
debug_worker:
overmind connect worker
docker:
docker build -t $(APP_NAME) -f ./docker/Dockerfile .
.PHONY: setup db_create db_migrate db_seed db console server burn docker run
.PHONY: setup db_create db_migrate db_seed db console server burn docker run force_run debug debug_worker

View File

@@ -11,7 +11,7 @@ function prepareData(data) {
function getChartOptions() {
var fontFamily =
'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
'PlusJakarta,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
return {
responsive: true,
legend: { labels: { fontFamily } },

View File

@@ -1,10 +1,10 @@
// Typography
$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
$base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif !default;
$heading-font-family: $base-font-family !default;
$base-font-size: 10px !default;
$base-font-size: 16px !default;
$base-line-height: 1.5 !default;
$heading-line-height: 1.2 !default;

View File

@@ -48,17 +48,22 @@ class Messages::MessageBuilder
def process_emails
return unless @conversation.inbox&.inbox_type == 'Email'
cc_emails = []
cc_emails = @params[:cc_emails].gsub(/\s+/, '').split(',') if @params[:cc_emails].present?
cc_emails = process_email_string(@params[:cc_emails])
bcc_emails = process_email_string(@params[:bcc_emails])
to_emails = process_email_string(@params[:to_emails])
bcc_emails = []
bcc_emails = @params[:bcc_emails].gsub(/\s+/, '').split(',') if @params[:bcc_emails].present?
all_email_addresses = cc_emails + bcc_emails
all_email_addresses = cc_emails + bcc_emails + to_emails
validate_email_addresses(all_email_addresses)
@message.content_attributes[:cc_emails] = cc_emails
@message.content_attributes[:bcc_emails] = bcc_emails
@message.content_attributes[:to_emails] = to_emails
end
def process_email_string(email_string)
return [] if email_string.blank?
email_string.gsub(/\s+/, '').split(',')
end
def validate_email_addresses(all_emails)

View File

@@ -20,7 +20,7 @@ class V2::ReportBuilder
# For backward compatible with old report
def build
if %w[avg_first_response_time avg_resolution_time].include?(params[:metric])
if %w[avg_first_response_time avg_resolution_time reply_time].include?(params[:metric])
timeseries.each_with_object([]) do |p, arr|
arr << { value: p[1], timestamp: p[0].in_time_zone(@timezone).to_i, count: @grouped_values.count[p[0]] }
end
@@ -33,18 +33,19 @@ class V2::ReportBuilder
def summary
{
conversations_count: conversations_count.values.sum,
incoming_messages_count: incoming_messages_count.values.sum,
outgoing_messages_count: outgoing_messages_count.values.sum,
conversations_count: conversations.count,
incoming_messages_count: incoming_messages.count,
outgoing_messages_count: outgoing_messages.count,
avg_first_response_time: avg_first_response_time_summary,
avg_resolution_time: avg_resolution_time_summary,
resolutions_count: resolutions_count.values.sum
resolutions_count: resolutions.count,
reply_time: reply_time_summary
}
end
def conversation_metrics
if params[:type].equal?(:account)
conversations
live_conversations
else
agent_metrics.sort_by { |hash| hash[:metric][:open] }.reverse
end
@@ -89,12 +90,12 @@ class V2::ReportBuilder
email: @user.email,
thumbnail: @user.avatar_url,
availability: account_user.availability_status,
metric: conversations
metric: live_conversations
}
end
end
def conversations
def live_conversations
@open_conversations = scope.conversations.where(account_id: @account.id).open
metric = {
open: @open_conversations.count,

View File

@@ -14,8 +14,18 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
@facebook_inbox = Current.account.inboxes.create!(name: inbox_name, channel: facebook_channel)
set_instagram_id(page_access_token, facebook_channel)
set_avatar(@facebook_inbox, page_id)
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
end
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
Rails.logger.error "Error in register_facebook_page: #{e.message}"
# Additional log statements
log_additional_info
end
def log_additional_info
Rails.logger.debug do
"user_access_token: #{params[:user_access_token]} , page_access_token: #{params[:page_access_token]} ,
page_id: #{params[:page_id]}, inbox_name: #{params[:inbox_name]}"
end
end
@@ -30,6 +40,8 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
instagram_id = response['instagram_business_account']['id']
facebook_channel.update(instagram_id: instagram_id)
rescue StandardError => e
Rails.logger.error "Error in set_instagram_id: #{e.message}"
end
# get params[:inbox_id], current_account. params[:omniauth_token]
@@ -61,6 +73,7 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
fb_page&.reauthorized!
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
Rails.logger.error "Error in update_fb_page: #{e.message}"
end
end
@@ -77,7 +90,7 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
koala = Koala::Facebook::OAuth.new(GlobalConfigService.load('FB_APP_ID', ''), GlobalConfigService.load('FB_APP_SECRET', ''))
koala.exchange_access_token_info(omniauth_token)['access_token']
rescue StandardError => e
Rails.logger.error e
Rails.logger.error "Error in long_lived_token: #{e.message}"
end
def mark_already_existing_facebook_pages(data)

View File

@@ -18,7 +18,11 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
end
def authenticate_twilio
client = Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
client = if permitted_params[:api_key_sid].present?
Twilio::REST::Client.new(permitted_params[:api_key_sid], permitted_params[:auth_token], permitted_params[:account_sid])
else
Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
end
client.messages.list(limit: 1)
end
@@ -40,6 +44,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
@twilio_channel = Current.account.twilio_sms.create!(
account_sid: permitted_params[:account_sid],
auth_token: permitted_params[:auth_token],
api_key_sid: permitted_params[:api_key_sid],
messaging_service_sid: permitted_params[:messaging_service_sid].presence,
phone_number: phone_number,
medium: medium
@@ -52,7 +57,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
def permitted_params
params.require(:twilio_channel).permit(
:account_id, :messaging_service_sid, :phone_number, :account_sid, :auth_token, :name, :medium
:account_id, :messaging_service_sid, :phone_number, :account_sid, :auth_token, :name, :medium, :api_key_sid
)
end
end

View File

@@ -4,6 +4,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
sort_on :name, internal_name: :order_on_name, type: :scope, scope_params: [:direction]
sort_on :phone_number, type: :string
sort_on :last_activity_at, internal_name: :order_on_last_activity_at, type: :scope, scope_params: [:direction]
sort_on :created_at, internal_name: :order_on_created_at, type: :scope, scope_params: [:direction]
sort_on :company, internal_name: :order_on_company_name, type: :scope, scope_params: [:direction]
sort_on :city, internal_name: :order_on_city, type: :scope, scope_params: [:direction]
sort_on :country, internal_name: :order_on_country_name, type: :scope, scope_params: [:direction]

View File

@@ -1,5 +1,4 @@
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
include EnsureCurrentAccountHelper
before_action :conversation
private

View File

@@ -1,4 +1,5 @@
class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_custom_filters, except: [:create]
before_action :fetch_custom_filter, only: [:show, :update, :destroy]
DEFAULT_FILTER_TYPE = 'conversation'.freeze

View File

@@ -63,7 +63,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end
def destroy
::DeleteObjectJob.perform_later(@inbox) if @inbox.present?
::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present?
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
end
@@ -124,7 +124,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def inbox_attributes
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
:lock_to_single_conversation, :portal_id]
:lock_to_single_conversation, :portal_id, :sender_name_type, :business_name]
end
def permitted_params(channel_attributes = [])

View File

@@ -1,27 +1,27 @@
class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::BaseController
before_action :check_admin_authorization?
before_action :fetch_hook, only: [:update, :destroy]
before_action :fetch_hook, only: [:update, :destroy, :list_all_channels]
def list_all_channels
@channels = channel_builder.fetch_channels
end
def create
ActiveRecord::Base.transaction do
builder = Integrations::Slack::HookBuilder.new(
account: Current.account,
code: params[:code],
inbox_id: params[:inbox_id]
)
@hook = builder.perform
create_chatwoot_slack_channel
end
hook_builder = Integrations::Slack::HookBuilder.new(
account: Current.account,
code: params[:code],
inbox_id: params[:inbox_id]
)
@hook = hook_builder.perform
end
def update
create_chatwoot_slack_channel
render json: @hook
@hook = channel_builder.update(permitted_params[:reference_id])
render json: { error: I18n.t('errors.slack.invalid_channel_id') }, status: :unprocessable_entity if @hook.blank?
end
def destroy
@hook.destroy!
head :ok
end
@@ -31,11 +31,11 @@ class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::Base
@hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'slack')
end
def create_chatwoot_slack_channel
channel = params[:channel] || 'customer-conversations'
builder = Integrations::Slack::ChannelBuilder.new(
hook: @hook, channel: channel
)
builder.perform
def channel_builder
Integrations::Slack::ChannelBuilder.new(hook: @hook)
end
def permitted_params
params.permit(:reference_id)
end
end

View File

@@ -17,7 +17,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
@message.update!(submitted_email: contact_email)
ContactIdentifyAction.new(
contact: @contact,
params: { email: contact_email, name: contact_name }
params: { email: contact_email, name: contact_name },
retain_original_contact_name: true
).perform
else
@message.update!(message_update_params[:message])

View File

@@ -1,6 +1,7 @@
class DashboardController < ActionController::Base
include SwitchLocale
before_action :set_application_pack
before_action :set_global_config
around_action :switch_locale
before_action :ensure_installation_onboarding, only: [:index]
@@ -14,7 +15,7 @@ class DashboardController < ActionController::Base
def set_global_config
@global_config = GlobalConfig.get(
'LOGO', 'LOGO_THUMBNAIL',
'LOGO', 'LOGO_DARK', 'LOGO_THUMBNAIL',
'INSTALLATION_NAME',
'WIDGET_BRAND_URL', 'TERMS_URL',
'PRIVACY_URL',
@@ -60,4 +61,12 @@ class DashboardController < ActionController::Base
GIT_SHA: GIT_HASH
}
end
def set_application_pack
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
'v3app'
else
'application'
end
end
end

View File

@@ -1,4 +1,7 @@
class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::InboxesController
include Events::Types
before_action :set_conversation, only: [:toggle_typing, :update_last_seen]
def index
@conversations = @contact_inbox.hmac_verified? ? @contact.conversations : @contact_inbox.conversations
end
@@ -7,12 +10,36 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
@conversation = create_conversation
end
def toggle_typing
case params[:typing_status]
when 'on'
trigger_typing_event(CONVERSATION_TYPING_ON)
when 'off'
trigger_typing_event(CONVERSATION_TYPING_OFF)
end
head :ok
end
def update_last_seen
@conversation.contact_last_seen_at = DateTime.now.utc
@conversation.save!
head :ok
end
private
def set_conversation
@conversation = @contact_inbox.contact.conversations.find_by!(display_id: params[:id])
end
def create_conversation
::Conversation.create!(conversation_params)
end
def trigger_typing_event(event)
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: @conversation.contact)
end
def conversation_params
{
account_id: @contact_inbox.contact.account_id,

View File

@@ -8,13 +8,22 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
def index
@articles = @portal.articles
@articles = @articles.search(list_params) if list_params.present?
@articles.order(position: :asc)
order_by_sort_param
@articles.page(list_params[:page]) if list_params[:page].present?
end
def show; end
private
def order_by_sort_param
@articles = if list_params[:sort].present? && list_params[:sort] == 'views'
@articles.order_by_views
else
@articles.order_by_position
end
end
def set_article
@article = @portal.articles.find_by(slug: permitted_params[:article_slug])
@article.increment_view_count
@@ -35,7 +44,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
end
def list_params
params.permit(:query, :locale)
params.permit(:query, :locale, :sort)
end
def permitted_params

View File

@@ -1,8 +1,13 @@
class Public::Api::V1::Portals::BaseController < PublicController
before_action :show_plain_layout
around_action :set_locale
private
def show_plain_layout
@is_plain_layout_enabled = params[:show_plain_layout] == 'true'
end
def set_locale(&)
switch_locale_with_portal(&) if params[:locale].present?
switch_locale_with_article(&) if params[:article_slug].present?

View File

@@ -86,7 +86,10 @@ class AccountDashboard < Administrate::BaseDashboard
"##{account.id} #{account.name}"
end
def permitted_attributes
# We do not use the action parameter but we still need to define it
# to prevent an error from being raised (wrong number of arguments)
# Reference: https://github.com/thoughtbot/administrate/pull/2356/files#diff-4e220b661b88f9a19ac527c50d6f1577ef6ab7b0bed2bfdf048e22e6bfa74a05R204
def permitted_attributes(action)
super + [limits: {}]
end
end

View File

@@ -6,7 +6,8 @@ class ConversationFinder
latest: 'latest',
sort_on_created_at: 'sort_on_created_at',
last_user_message_at: 'last_user_message_at',
sort_on_priority: 'sort_on_priority'
sort_on_priority: 'sort_on_priority',
sort_on_waiting_since: 'sort_on_waiting_since'
}.with_indifferent_access
# assumptions

View File

@@ -0,0 +1,21 @@
module BillingHelper
private
def default_plan?(account)
installation_config = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS')
default_plan = installation_config&.value&.first
# Return false if not plans are configured, so that no checks are enforced
return false if default_plan.blank?
account.custom_attributes['plan_name'].nil? || account.custom_attributes['plan_name'] == default_plan['name']
end
def conversations_this_month(account)
account.conversations.where('created_at > ?', 30.days.ago).count
end
def non_web_inboxes(account)
account.inboxes.where.not(channel_type: Channel::WebWidget.to_s).count
end
end

View File

@@ -17,21 +17,36 @@ module ReportHelper
end
def conversations_count
(get_grouped_values scope.conversations.where(account_id: account.id)).count
(get_grouped_values conversations).count
end
def incoming_messages_count
(get_grouped_values scope.messages.where(account_id: account.id).incoming.unscope(:order)).count
(get_grouped_values incoming_messages).count
end
def outgoing_messages_count
(get_grouped_values scope.messages.where(account_id: account.id).outgoing.unscope(:order)).count
(get_grouped_values outgoing_messages).count
end
def resolutions_count
object_scope = scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved,
conversations: { status: :resolved }).distinct
(get_grouped_values object_scope).count
(get_grouped_values resolutions).count
end
def conversations
scope.conversations.where(account_id: account.id, created_at: range)
end
def incoming_messages
scope.messages.where(account_id: account.id, created_at: range).incoming.unscope(:order)
end
def outgoing_messages
scope.messages.where(account_id: account.id, created_at: range).outgoing.unscope(:order)
end
def resolutions
scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved,
conversations: { status: :resolved }, created_at: range).distinct
end
def avg_first_response_time
@@ -41,6 +56,13 @@ module ReportHelper
grouped_reporting_events.average(:value)
end
def reply_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'reply_time', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
grouped_reporting_events.average(:value)
end
def avg_resolution_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
@@ -51,17 +73,35 @@ module ReportHelper
def avg_resolution_time_summary
reporting_events = scope.reporting_events
.where(name: 'conversation_resolved', account_id: account.id, created_at: range)
avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
avg_rt = if params[:business_hours].present?
reporting_events.average(:value_in_business_hours)
else
reporting_events.average(:value)
end
return 0 if avg_rt.blank?
avg_rt
end
def reply_time_summary
reporting_events = scope.reporting_events
.where(name: 'reply_time', account_id: account.id, created_at: range)
reply_time = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
return 0 if reply_time.blank?
reply_time
end
def avg_first_response_time_summary
reporting_events = scope.reporting_events
.where(name: 'first_response', account_id: account.id, created_at: range)
avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
avg_frt = if params[:business_hours].present?
reporting_events.average(:value_in_business_hours)
else
reporting_events.average(:value)
end
return 0 if avg_frt.blank?

View File

@@ -4,8 +4,13 @@
id="app"
class="app-wrapper app-root"
:class="{ 'app-rtl--wrapper': isRTLView }"
:dir="isRTLView ? 'rtl' : 'ltr'"
>
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
<payment-pending-banner />
<upgrade-banner />
</template>
<transition name="fade" mode="out-in">
<router-view />
</transition>
@@ -25,9 +30,12 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
import LoadingState from './components/widgets/LoadingState.vue';
import NetworkNotification from './components/NetworkNotification';
import UpdateBanner from './components/app/UpdateBanner.vue';
import UpgradeBanner from './components/app/UpgradeBanner.vue';
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer';
import rtlMixin from 'shared/mixins/rtlMixin';
import { setColorTheme } from './helper/themeHelper';
import {
registerSubscription,
verifyServiceWorkerExistence,
@@ -41,7 +49,9 @@ export default {
LoadingState,
NetworkNotification,
UpdateBanner,
PaymentPendingBanner,
WootSnackbarBox,
UpgradeBanner,
},
mixins: [rtlMixin],
@@ -59,6 +69,7 @@ export default {
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
authUIFlags: 'getAuthUIFlags',
accountUIFlags: 'accounts/getUIFlags',
currentAccountId: 'getCurrentAccountId',
}),
hasAccounts() {
@@ -80,9 +91,18 @@ export default {
},
},
mounted() {
this.initializeColorTheme();
this.listenToThemeChanges();
this.setLocale(window.chatwootConfig.selectedLocale);
},
methods: {
initializeColorTheme() {
setColorTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
},
listenToThemeChanges() {
const mql = window.matchMedia('(prefers-color-scheme: dark)');
mql.onchange = e => setColorTheme(e.matches);
},
setLocale(locale) {
this.$root.$i18n.locale = locale;
},

View File

@@ -18,6 +18,10 @@ class CacheEnabledApiClient extends ApiClient {
return this.getFromCache();
}
return this.getFromNetwork();
}
getFromNetwork() {
return axios.get(this.url);
}
@@ -32,7 +36,12 @@ class CacheEnabledApiClient extends ApiClient {
}
async getFromCache() {
await this.dataManager.initDb();
try {
// IDB is not supported in Firefox private mode: https://bugzilla.mozilla.org/show_bug.cgi?id=781982
await this.dataManager.initDb();
} catch {
return this.getFromNetwork();
}
const { data } = await axios.get(
`/api/v1/accounts/${this.accountIdFromRoute}/cache_keys`
@@ -55,16 +64,22 @@ class CacheEnabledApiClient extends ApiClient {
}
async refetchAndCommit(newKey = null) {
await this.dataManager.initDb();
const response = await axios.get(this.url);
this.dataManager.replace({
modelName: this.cacheModelName,
data: this.extractDataFromResponse(response),
});
const response = await this.getFromNetwork();
await this.dataManager.setCacheKeys({
[this.cacheModelName]: newKey,
});
try {
await this.dataManager.initDb();
this.dataManager.replace({
modelName: this.cacheModelName,
data: this.extractDataFromResponse(response),
});
await this.dataManager.setCacheKeys({
[this.cacheModelName]: newKey,
});
} catch {
// Ignore error
}
return response;
}

View File

@@ -3,47 +3,11 @@
import Cookies from 'js-cookie';
import endPoints from './endPoints';
import {
setAuthCredentials,
clearCookiesOnLogout,
deleteIndexedDBOnLogout,
} from '../store/utils/api';
export default {
login(creds) {
return new Promise((resolve, reject) => {
axios
.post('auth/sign_in', creds)
.then(response => {
setAuthCredentials(response);
resolve(response.data);
})
.catch(error => {
reject(error.response);
});
});
},
register(creds) {
const urlData = endPoints('register');
const fetchPromise = new Promise((resolve, reject) => {
axios
.post(urlData.url, {
account_name: creds.accountName.trim(),
user_full_name: creds.fullName.trim(),
email: creds.email,
password: creds.password,
h_captcha_client_response: creds.hCaptchaClientResponse,
})
.then(response => {
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
reject(error);
});
});
return fetchPromise;
},
validityCheck() {
const urlData = endPoints('validityCheck');
return axios.get(urlData.url);
@@ -73,45 +37,6 @@ export default {
}
return false;
},
verifyPasswordToken({ confirmationToken }) {
return new Promise((resolve, reject) => {
axios
.post('auth/confirmation', {
confirmation_token: confirmationToken,
})
.then(response => {
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
reject(error.response);
});
});
},
setNewPassword({ resetPasswordToken, password, confirmPassword }) {
return new Promise((resolve, reject) => {
axios
.put('auth/password', {
reset_password_token: resetPasswordToken,
password_confirmation: confirmPassword,
password,
})
.then(response => {
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
reject(error.response);
});
});
},
resetPassword({ email }) {
const urlData = endPoints('resetPassword');
return axios.post(urlData.url, { email });
},
profileUpdate({
password,
password_confirmation,

View File

@@ -13,6 +13,10 @@ class EnterpriseAccountAPI extends ApiClient {
subscription() {
return axios.post(`${this.url}subscription`);
}
getLimits() {
return axios.get(`${this.url}limits`);
}
}
export default new EnterpriseAccountAPI();

View File

@@ -10,6 +10,7 @@ export const buildCreatePayload = ({
files,
ccEmails = '',
bccEmails = '',
toEmails = '',
templateParams,
}) => {
let payload;
@@ -25,6 +26,9 @@ export const buildCreatePayload = ({
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
payload.append('bcc_emails', bccEmails);
if (toEmails) {
payload.append('to_emails', toEmails);
}
} else {
payload = {
content: message,
@@ -33,6 +37,7 @@ export const buildCreatePayload = ({
content_attributes: contentAttributes,
cc_emails: ccEmails,
bcc_emails: bccEmails,
to_emails: toEmails,
template_params: templateParams,
};
}
@@ -53,6 +58,7 @@ class MessageApi extends ApiClient {
files,
ccEmails = '',
bccEmails = '',
toEmails = '',
templateParams,
}) {
return axios({
@@ -66,6 +72,7 @@ class MessageApi extends ApiClient {
files,
ccEmails,
bccEmails,
toEmails,
templateParams,
}),
});

View File

@@ -8,11 +8,19 @@ class IntegrationsAPI extends ApiClient {
}
connectSlack(code) {
return axios.post(`${this.baseUrl()}/integrations/slack`, {
code: code,
return axios.post(`${this.baseUrl()}/integrations/slack`, { code });
}
updateSlack({ referenceId }) {
return axios.patch(`${this.baseUrl()}/integrations/slack`, {
reference_id: referenceId,
});
}
listAllSlackChannels() {
return axios.get(`${this.baseUrl()}/integrations/slack/list_all_channels`);
}
delete(integrationId) {
return axios.delete(`${this.baseUrl()}/integrations/${integrationId}`);
}

View File

@@ -2,18 +2,62 @@
import ApiClient from '../ApiClient';
/**
* Represents the data object for a OpenAI hook.
* @typedef {Object} ConversationMessageData
* @property {string} [tone] - The tone of the message.
* @property {string} [content] - The content of the message.
* @property {string} [conversation_display_id] - The display ID of the conversation (optional).
*/
/**
* A client for the OpenAI API.
* @extends ApiClient
*/
class OpenAIAPI extends ApiClient {
/**
* Creates a new OpenAIAPI instance.
*/
constructor() {
super('integrations', { accountScoped: true });
/**
* The conversation events supported by the API.
* @type {string[]}
*/
this.conversation_events = [
'summarize',
'reply_suggestion',
'label_suggestion',
];
/**
* The message events supported by the API.
* @type {string[]}
*/
this.message_events = ['rephrase'];
}
/**
* Processes an event using the OpenAI API.
* @param {Object} options - The options for the event.
* @param {string} [options.type='rephrase'] - The type of event to process.
* @param {string} [options.content] - The content of the event.
* @param {string} [options.tone] - The tone of the event.
* @param {string} [options.conversationId] - The ID of the conversation to process the event for.
* @param {string} options.hookId - The ID of the hook to use for processing the event.
* @returns {Promise} A promise that resolves with the result of the event processing.
*/
processEvent({ type = 'rephrase', content, tone, conversationId, hookId }) {
/**
* @type {ConversationMessageData}
*/
let data = {
tone,
content,
};
if (type === 'reply_suggestion' || type === 'summarize') {
if (this.conversation_events.includes(type)) {
data = {
conversation_display_id: conversationId,
};

View File

@@ -11,7 +11,9 @@ describe('#integrationAPI', () => {
expect(integrationAPI).toHaveProperty('update');
expect(integrationAPI).toHaveProperty('delete');
expect(integrationAPI).toHaveProperty('connectSlack');
expect(integrationAPI).toHaveProperty('createHook');
expect(integrationAPI).toHaveProperty('updateSlack');
expect(integrationAPI).toHaveProperty('updateSlack');
expect(integrationAPI).toHaveProperty('listAllSlackChannels');
expect(integrationAPI).toHaveProperty('deleteHook');
});
describeWithAPIMock('API calls', context => {
@@ -26,6 +28,24 @@ describe('#integrationAPI', () => {
);
});
it('#updateSlack', () => {
const updateObj = { referenceId: 'SDFSDGSVE' };
integrationAPI.updateSlack(updateObj);
expect(context.axiosMock.patch).toHaveBeenCalledWith(
'/api/v1/integrations/slack',
{
reference_id: updateObj.referenceId,
}
);
});
it('#listAllSlackChannels', () => {
integrationAPI.listAllSlackChannels();
expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v1/integrations/slack/list_all_channels'
);
});
it('#delete', () => {
integrationAPI.delete(2);
expect(context.axiosMock.delete).toHaveBeenCalledWith(

View File

@@ -0,0 +1,34 @@
<svg width="495" height="384" viewBox="0 0 495 384" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="No Chat History">
<path id="Fill 1" fill-rule="evenodd" clip-rule="evenodd" d="M266.903 0C221.687 0 180.128 15.7196 147.313 42.0198H94.2771C87.3866 42.0198 81.749 47.6925 81.749 54.6257C81.749 61.559 87.3866 67.2317 94.2771 67.2317H121.268H129.178C136.068 67.2317 141.706 72.9043 141.706 79.8376C141.706 83.3 140.294 86.4599 138.023 88.7374C135.759 91.0233 132.619 92.4435 129.178 92.4435H112.303H102.986H57.5281C50.6376 92.4435 45 98.1162 45 105.049C45 111.983 50.6376 117.655 57.5281 117.655H90.071C80.2473 140.886 74.8059 166.446 74.8059 193.291C74.8059 203.031 75.5242 212.603 76.9023 221.957C77.4786 225.865 78.1718 229.739 78.982 233.571C102.618 229.932 127.691 227.008 153.866 224.873L155.837 226.68L157.692 224.571C189.137 222.108 222.144 220.797 256.187 220.797C328.516 220.797 396.201 226.714 454.072 237C456.302 227.268 457.806 217.259 458.516 207.023C458.841 202.485 459 197.905 459 193.291C459 86.5356 372.999 0 266.903 0Z" fill="#43484C"/>
<path id="Fill 3" fill-rule="evenodd" clip-rule="evenodd" d="M20.2465 91.4282H12.6678C5.7005 91.4282 0 97.1882 0 104.228C0 111.268 5.7005 117.028 12.6678 117.028H20.2465C27.2137 117.028 32.9142 111.268 32.9142 104.228C32.9142 97.1882 27.2137 91.4282 20.2465 91.4282Z" fill="#43484C"/>
<g id="Group 7">
<mask id="mask0_201_141" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="78" y="219" width="417" height="165">
<path id="Clip 6" fill-rule="evenodd" clip-rule="evenodd" d="M495 219V384H286.5H78V219H495Z" fill="white"/>
</mask>
<g mask="url(#mask0_201_141)">
<path id="Fill 5" fill-rule="evenodd" clip-rule="evenodd" d="M495 279.775C495 286.675 489.336 292.321 482.413 292.321H402.016C398.559 292.321 395.405 293.734 393.131 296.009C390.849 298.276 389.43 301.421 389.43 304.867C389.43 311.768 395.094 317.413 402.016 317.413H416.954C420.41 317.413 423.566 318.827 425.84 321.102C428.122 323.369 429.54 326.514 429.54 329.96C429.54 336.86 423.876 342.506 416.954 342.506H382.608C350.344 366.662 310.251 380.981 266.802 380.981C174.004 380.981 96.5026 315.699 78 228.695C101.747 225.073 126.937 222.162 153.235 220.038L155.215 221.836L157.079 219.736C188.671 217.286 221.833 215.981 256.036 215.981C328.703 215.981 396.705 221.869 454.847 232.107C452.02 244.302 448.026 256.045 442.991 267.228H482.413C485.879 267.228 489.017 268.642 491.3 270.917C493.582 273.184 495 276.328 495 279.775Z" fill="#34393D"/>
</g>
</g>
<path id="Fill 8" fill-rule="evenodd" clip-rule="evenodd" d="M459 206.12C458.285 216.665 456.771 226.975 454.525 237C396.235 226.404 328.06 220.31 255.208 220.31C220.919 220.31 187.673 221.66 156 224.196L177.965 198.615L212.134 221.028L258.388 208.19L337.959 220.31L372.503 208.19L381.252 220.31L402.126 204.736L421.632 222.707L452.396 198L459 206.12Z" fill="#55575E"/>
<path id="Fill 10" fill-rule="evenodd" clip-rule="evenodd" d="M344.905 216.502C353.565 203.915 358.526 188.987 358.397 173.012C358.043 128.578 318.46 92.8734 269.986 93.2602C221.512 93.6478 182.505 129.983 182.86 174.416C183.214 218.849 222.798 254.555 271.271 254.168C288.59 254.03 304.7 249.3 318.238 241.253L354.454 249.78L344.905 216.502Z" fill="#686B73"/>
<path id="Fill 12" fill-rule="evenodd" clip-rule="evenodd" d="M245.029 171.813C245.069 176.862 241.008 180.988 235.959 181.028C230.909 181.069 226.783 177.008 226.743 171.959C226.703 166.909 230.763 162.784 235.813 162.743C240.861 162.703 244.987 166.763 245.029 171.813Z" fill="#BEC0D6"/>
<path id="Fill 14" fill-rule="evenodd" clip-rule="evenodd" d="M277.942 171.812C277.982 176.862 273.922 180.988 268.872 181.028C263.822 181.068 259.696 177.008 259.657 171.959C259.616 166.909 263.677 162.783 268.726 162.743C273.775 162.703 277.901 166.763 277.942 171.812Z" fill="#BEC0D6"/>
<path id="Fill 16" fill-rule="evenodd" clip-rule="evenodd" d="M310.856 171.812C310.897 176.861 306.836 180.987 301.786 181.028C296.737 181.068 292.611 177.008 292.571 171.958C292.53 166.908 296.591 162.783 301.641 162.743C306.689 162.702 310.815 166.763 310.856 171.812Z" fill="#BEC0D6"/>
<path id="Fill 20" fill-rule="evenodd" clip-rule="evenodd" d="M173.714 235.887C173.714 241.693 175.571 247.099 178.763 251.649L175.376 263.789L188.564 260.581C193.523 263.465 199.402 265.143 205.714 265.143C223.388 265.143 237.714 252.044 237.714 235.887C237.714 219.728 223.388 206.629 205.714 206.629C188.041 206.629 173.714 219.728 173.714 235.887Z" fill="url(#paint0_linear_201_141)"/>
<g id="Frame 148">
<path id="Fill 26" fill-rule="evenodd" clip-rule="evenodd" d="M189 234.658C189 236.677 190.637 238.314 192.657 238.314C194.676 238.314 196.314 236.677 196.314 234.658C196.314 232.638 194.676 231 192.657 231C190.637 231 189 232.638 189 234.658Z" fill="#0D0D0D"/>
<path id="Fill 24" fill-rule="evenodd" clip-rule="evenodd" d="M202.314 234.658C202.314 236.677 203.951 238.314 205.971 238.314C207.99 238.314 209.628 236.677 209.628 234.658C209.628 232.638 207.99 231 205.971 231C203.951 231 202.314 232.638 202.314 234.658Z" fill="#0D0D0D"/>
<path id="Fill 22" fill-rule="evenodd" clip-rule="evenodd" d="M215.629 234.658C215.629 236.677 217.265 238.314 219.285 238.314C221.305 238.314 222.943 236.677 222.943 234.658C222.943 232.638 221.305 231 219.285 231C217.265 231 215.629 232.638 215.629 234.658Z" fill="#0D0D0D"/>
</g>
<path id="Fill 28" fill-rule="evenodd" clip-rule="evenodd" d="M153.6 223.557C127.481 225.685 102.461 228.601 78.8755 232.228C78.0671 228.408 77.3754 224.546 76.8003 220.65L117.229 190.171L153.6 223.557Z" fill="#55575E"/>
<path id="Fill 30" fill-rule="evenodd" clip-rule="evenodd" d="M336.457 286.171C336.457 289.706 304.529 292.571 265.143 292.571C225.757 292.571 193.829 289.706 193.829 286.171C193.829 282.638 225.757 279.771 265.143 279.771C304.529 279.771 336.457 282.638 336.457 286.171Z" fill="#686B73"/>
<path id="Fill 32" fill-rule="evenodd" clip-rule="evenodd" d="M453.486 345.599C446.446 345.599 440.686 339.839 440.686 332.799C440.686 325.76 446.446 320 453.486 320C460.526 320 466.286 325.76 466.286 332.799C466.286 339.839 460.526 345.599 453.486 345.599Z" fill="#34393D"/>
</g>
<defs>
<linearGradient id="paint0_linear_201_141" x1="205.714" y1="177.372" x2="147.434" y2="241.115" gradientUnits="userSpaceOnUse">
<stop stop-color="#9DC9FB"/>
<stop offset="1" stop-color="#9DC9FB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,33 @@
<svg width="495" height="387" viewBox="0 0 495 387" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="No Chat History">
<path id="Fill 1" fill-rule="evenodd" clip-rule="evenodd" d="M267.927 1.5C222.792 1.5 181.307 17.1457 148.551 43.3222H95.611C88.7329 43.3222 83.1054 48.9682 83.1054 55.8688C83.1054 62.7695 88.7329 68.4155 95.611 68.4155H122.554H130.449C137.327 68.4155 142.955 74.0615 142.955 80.9621C142.955 84.4083 141.546 87.5533 139.278 89.8201C137.019 92.0952 133.884 93.5088 130.449 93.5088H113.605H104.304H58.9279C52.0499 93.5088 46.4224 99.1548 46.4224 106.055C46.4224 112.956 52.0499 118.602 58.9279 118.602H91.4124C81.6064 141.723 76.1748 167.164 76.1748 193.882C76.1748 203.576 76.8918 213.103 78.2674 222.413C78.8426 226.303 79.5346 230.159 80.3433 233.973C103.937 230.351 128.965 227.44 155.093 225.316L157.061 227.114L158.912 225.014C190.301 222.564 223.249 221.259 257.23 221.259C329.429 221.259 396.993 227.147 454.76 237.385C456.986 227.699 458.487 217.737 459.196 207.549C459.521 203.033 459.679 198.474 459.679 193.882C459.679 87.6286 373.832 1.5 267.927 1.5Z" fill="#F1F2F7"/>
<path id="Fill 3" fill-rule="evenodd" clip-rule="evenodd" d="M20.954 92.9282H13.3753C6.40802 92.9282 0.70752 98.6882 0.70752 105.728C0.70752 112.768 6.40802 118.528 13.3753 118.528H20.954C27.9213 118.528 33.6218 112.768 33.6218 105.728C33.6218 98.6882 27.9213 92.9282 20.954 92.9282Z" fill="#F1F2F7"/>
<g id="Group 7">
<mask id="mask0_156_62" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="80" y="221" width="415" height="165">
<path id="Clip 6" fill-rule="evenodd" clip-rule="evenodd" d="M494.293 221.536V385.5H287.188H80.083V221.536H494.293Z" fill="white"/>
</mask>
<g mask="url(#mask0_156_62)">
<path id="Fill 5" fill-rule="evenodd" clip-rule="evenodd" d="M494.293 284.929C494.293 291.786 488.667 297.396 481.79 297.396H401.932C398.498 297.396 395.364 298.801 393.106 301.062C390.838 303.314 389.429 306.439 389.429 309.864C389.429 316.721 395.055 322.331 401.932 322.331H416.769C420.202 322.331 423.337 323.736 425.596 325.997C427.863 328.249 429.271 331.374 429.271 334.799C429.271 341.656 423.645 347.266 416.769 347.266H382.653C350.604 371.271 310.78 385.5 267.622 385.5C175.444 385.5 98.4618 320.627 80.083 234.169C103.671 230.57 128.693 227.678 154.815 225.567L156.781 227.354L158.632 225.268C190.013 222.832 222.954 221.536 256.928 221.536C329.108 221.536 396.656 227.387 454.409 237.561C451.601 249.679 447.633 261.349 442.632 272.461H481.79C485.233 272.461 488.35 273.866 490.617 276.127C492.884 278.379 494.293 281.504 494.293 284.929Z" fill="#E8EBF2"/>
</g>
</g>
<path id="Fill 8" fill-rule="evenodd" clip-rule="evenodd" d="M457.85 206.981C457.142 217.363 455.643 227.515 453.42 237.386C395.73 226.953 328.256 220.953 256.153 220.953C222.216 220.953 189.312 222.282 157.964 224.779L179.704 199.591L213.521 221.659L259.3 209.019L338.053 220.953L372.242 209.019L380.901 220.953L401.56 205.618L420.866 223.313L451.314 198.986L457.85 206.981Z" fill="#E0E2EE"/>
<path id="Fill 10" fill-rule="evenodd" clip-rule="evenodd" d="M345.614 218.002C354.273 205.415 359.234 190.487 359.106 174.512C358.752 130.078 319.168 94.3734 270.694 94.7602C222.22 95.1478 183.213 131.483 183.568 175.916C183.923 220.349 223.506 256.055 271.979 255.668C289.298 255.53 305.408 250.8 318.946 242.753L355.162 251.28L345.614 218.002Z" fill="#D8DBEA"/>
<path id="Fill 12" fill-rule="evenodd" clip-rule="evenodd" d="M245.736 173.313C245.776 178.362 241.716 182.488 236.667 182.528C231.616 182.569 227.49 178.508 227.451 173.459C227.411 168.409 231.471 164.284 236.52 164.243C241.569 164.203 245.695 168.263 245.736 173.313Z" fill="#BEC0D6"/>
<path id="Fill 14" fill-rule="evenodd" clip-rule="evenodd" d="M278.65 173.312C278.69 178.362 274.629 182.488 269.58 182.528C264.53 182.568 260.404 178.508 260.365 173.459C260.323 168.409 264.384 164.283 269.434 164.243C274.483 164.203 278.609 168.263 278.65 173.312Z" fill="#BEC0D6"/>
<path id="Fill 16" fill-rule="evenodd" clip-rule="evenodd" d="M311.565 173.312C311.605 178.361 307.544 182.487 302.495 182.528C297.445 182.568 293.319 178.508 293.28 173.458C293.239 168.408 297.299 164.283 302.349 164.243C307.398 164.202 311.524 168.263 311.565 173.312Z" fill="#BEC0D6"/>
<path id="Fill 18" fill-rule="evenodd" clip-rule="evenodd" d="M157.965 224.585L156.192 226.414L154.308 224.848C155.521 224.753 156.744 224.666 157.965 224.585Z" fill="#F1F2F7"/>
<path id="Fill 20" fill-rule="evenodd" clip-rule="evenodd" d="M174.422 237.387C174.422 243.193 176.279 248.599 179.47 253.149L176.083 265.289L189.272 262.081C194.23 264.965 200.11 266.643 206.422 266.643C224.095 266.643 238.422 253.544 238.422 237.387C238.422 221.228 224.095 208.129 206.422 208.129C188.748 208.129 174.422 221.228 174.422 237.387Z" fill="url(#paint0_linear_156_62)"/>
<path id="Fill 22" fill-rule="evenodd" clip-rule="evenodd" d="M214.651 237.386C214.651 239.405 216.287 241.043 218.308 241.043C220.327 241.043 221.965 239.405 221.965 237.386C221.965 235.366 220.327 233.728 218.308 233.728C216.287 233.728 214.651 235.366 214.651 237.386Z" fill="white"/>
<path id="Fill 24" fill-rule="evenodd" clip-rule="evenodd" d="M203.679 237.386C203.679 239.405 205.316 241.043 207.336 241.043C209.355 241.043 210.993 239.405 210.993 237.386C210.993 235.366 209.355 233.728 207.336 233.728C205.316 233.728 203.679 235.366 203.679 237.386Z" fill="white"/>
<path id="Fill 26" fill-rule="evenodd" clip-rule="evenodd" d="M190.879 237.386C190.879 239.405 192.516 241.043 194.537 241.043C196.556 241.043 198.194 239.405 198.194 237.386C198.194 235.366 196.556 233.728 194.537 233.728C192.516 233.728 190.879 235.366 190.879 237.386Z" fill="white"/>
<path id="Fill 28" fill-rule="evenodd" clip-rule="evenodd" d="M154.308 225.057C128.188 227.185 103.169 230.101 79.583 233.728C78.7746 229.908 78.0829 226.046 77.5078 222.15L117.936 191.671L154.308 225.057Z" fill="#E0E2EE"/>
<path id="Fill 30" fill-rule="evenodd" clip-rule="evenodd" d="M337.164 287.671C337.164 291.206 305.235 294.071 265.85 294.071C226.464 294.071 194.536 291.206 194.536 287.671C194.536 284.138 226.464 281.271 265.85 281.271C305.235 281.271 337.164 284.138 337.164 287.671Z" fill="#D8DBEA"/>
<path id="Fill 32" fill-rule="evenodd" clip-rule="evenodd" d="M454.193 347.099C447.153 347.099 441.393 341.339 441.393 334.299C441.393 327.26 447.153 321.5 454.193 321.5C461.233 321.5 466.993 327.26 466.993 334.299C466.993 341.339 461.233 347.099 454.193 347.099Z" fill="#E1E3EF"/>
</g>
<defs>
<linearGradient id="paint0_linear_156_62" x1="206.422" y1="178.872" x2="148.142" y2="242.615" gradientUnits="userSpaceOnUse">
<stop stop-color="#9DC9FB"/>
<stop offset="1" stop-color="#9DC9FB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,37 +1,64 @@
@import '~vue2-datepicker/scss/index';
.mx-datepicker-popup {
z-index: 99999;
@apply z-[99999];
}
.date-picker {
&.no-margin {
.mx-input {
margin-bottom: 0;
@apply mb-0;
}
}
&:not(.auto-width) {
.mx-datepicker-range {
width: 320px;
@apply w-[320px];
}
}
.mx-datepicker {
width: 100%;
@apply w-full;
}
.mx-input {
border: 1px solid var(--s-200);
border-radius: var(--border-radius-normal);
box-shadow: none;
display: flex;
height: 4.0rem;
@apply h-[2.5rem] flex border border-solid border-slate-200 dark:border-slate-600 rounded-md shadow-none;
}
.mx-input:disabled,
.mx-input[readonly] {
background-color: var(--white);
cursor: pointer;
@apply bg-white dark:bg-slate-900 cursor-pointer;
}
}
.mx-calendar-content .cell:hover {
@apply bg-slate-75 dark:bg-slate-700 text-slate-900 dark:text-slate-100;
}
.mx-datepicker-inline {
@apply w-full;
.mx-calendar {
@apply w-full;
}
.cell.disabled {
@apply bg-slate-25 dark:bg-slate-900 text-slate-200 dark:text-slate-300;
}
.mx-time-item.disabled {
@apply bg-slate-25 dark:bg-slate-900;
}
.today {
@apply font-semibold;
}
.mx-datepicker-main {
@apply border-0 bg-white dark:bg-slate-800;
}
.mx-time-header {
@apply border-0;
}
}

View File

@@ -8,7 +8,7 @@
}
select {
height: 4.0rem;
height: 2.5rem;
}
.card {
@@ -16,19 +16,6 @@ select {
padding: var(--space-normal);
}
.button-wrapper .button.grey-btn {
margin-left: var(--space-normal);
}
.tooltip {
background-color: var(--black-transparent);
border-radius: $space-smaller;
font-size: $font-size-mini;
max-width: var(--space-giga);
padding: $space-smaller $space-small;
z-index: 999;
}
code {
border: 0;
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
@@ -39,6 +26,7 @@ code {
background: $color-background;
border-radius: var(--border-radius-large);
padding: $space-two;
@apply bg-slate-50 dark:bg-slate-600 text-slate-800 dark:text-slate-100;
}
}

View File

@@ -48,7 +48,7 @@
// Disable contrast warnings in Foundation.
$contrast-warnings: false;
$global-font-size: 10px;
$global-font-size: 16px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (primary: $color-woot,
@@ -63,7 +63,7 @@ $black: #000;
$white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'Inter',
$body-font-family: 'PlusJakarta',
-apple-system,
system-ui,
BlinkMacSystemFont,
@@ -117,7 +117,7 @@ $header-font-style: normal;
$font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.5rem;
$header-margin-bottom: 0.3125rem;
$header-styles: (small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
@@ -133,7 +133,7 @@ $header-styles: (small: ("h1": ("font-size": 24),
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.45;
$paragraph-lineheight: 1.65;
$paragraph-margin-bottom: var(--space-small);
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
@@ -153,11 +153,11 @@ $list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$list-side-margin: 1.25rem;
$list-nested-side-margin: 1.25rem;
$defnlist-margin-bottom: 1rem;
$list-side-margin: 0.78125rem;
$list-nested-side-margin: 0.78125rem;
$defnlist-margin-bottom: 0.6875rem;
$defnlist-term-weight: $global-weight-bold;
$defnlist-term-margin-bottom: 0.3rem;
$defnlist-term-margin-bottom: 0.1875rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
@@ -179,9 +179,9 @@ $lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.2rem;
$subheader-margin-bottom: 0.5rem;
$stat-font-size: 2.5rem;
$subheader-margin-top: 0.125rem;
$subheader-margin-bottom: 0.3125rem;
$stat-font-size: 1.5625rem;
// 6. Abide
// --------
@@ -202,11 +202,11 @@ $accordion-plusminus: true;
$accordion-title-font-size: rem-calc(12);
$accordion-item-color: $primary-color;
$accordion-item-background-hover: $light-gray;
$accordion-item-padding: 1.25rem 1rem;
$accordion-item-padding: 0.78125rem 0.625rem;
$accordion-content-background: $white;
$accordion-content-border: 1px solid $light-gray;
$accordion-content-color: $body-font-color;
$accordion-content-padding: 1rem;
$accordion-content-padding: 0.625rem;
// 8. Accordion Menu
// -----------------
@@ -234,7 +234,7 @@ $breadcrumbs-item-font-size: rem-calc(11);
$breadcrumbs-item-color: $primary-color;
$breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.75rem;
$breadcrumbs-item-margin: 0.46875rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
@@ -275,8 +275,8 @@ $buttongroup-radius-on-each: false;
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$callout-margin: 0 0 1rem 0;
$callout-padding: 1rem;
$callout-margin: 0 0 0.625rem 0;
$callout-padding: 0.625rem;
$callout-font-color: $body-font-color;
$callout-font-color-alt: $body-background;
$callout-radius: $global-radius;
@@ -320,10 +320,10 @@ $drilldown-background: $white;
// 17. Dropdown
// ------------
$dropdown-padding: 1rem;
$dropdown-padding: 0.625rem;
$dropdown-background: $body-background;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 1rem;
$dropdown-font-size: 0.625rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (tiny: 100px,
@@ -354,7 +354,7 @@ $helptext-font-style: italic;
$input-prefix-color: $color-body;
$input-prefix-background: var(--b-100);
$input-prefix-border: 1px solid $color-border;
$input-prefix-padding: 1rem;
$input-prefix-padding: 0.625rem;
$form-label-color: $color-body;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $font-weight-medium;
@@ -406,14 +406,14 @@ $menu-margin-nested: $space-medium;
$menu-item-padding: $space-slab;
$menu-item-color-active: $white;
$menu-item-background-active: $color-background;
$menu-icon-spacing: 0.25rem;
$menu-icon-spacing: 0.15625rem;
$menu-item-background-hover: $light-gray;
$menu-border: $light-gray;
// 23. Meter
// ---------
$meter-height: 1rem;
$meter-height: 0.625rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
@@ -423,11 +423,11 @@ $meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-sizes: (small: 23rem,
medium: 23rem,
$offcanvas-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-vertical-sizes: (small: 23rem,
medium: 23rem,
$offcanvas-vertical-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-background: $light-gray;
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
@@ -445,14 +445,14 @@ $maincontent-class: 'off-canvas-content';
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 1.2rem;
$orbit-bullet-margin: 0.1rem;
$orbit-bullet-margin-top: 0.8rem;
$orbit-bullet-margin-bottom: 0.8rem;
$orbit-bullet-diameter: 0.75rem;
$orbit-bullet-margin: 0.0625rem;
$orbit-bullet-margin-top: 0.5rem;
$orbit-bullet-margin-bottom: 0.5rem;
$orbit-caption-background: rgba($black, 0.5);
$orbit-caption-padding: 1rem;
$orbit-caption-padding: 0.625rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 1rem;
$orbit-control-padding: 0.625rem;
$orbit-control-zindex: 10;
// 26. Pagination
@@ -476,7 +476,7 @@ $pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 1rem;
$progress-height: 0.625rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
@@ -504,13 +504,13 @@ $reveal-overlay-background: rgba($black, 0.45);
// 30. Slider
// ----------
$slider-width-vertical: 0.5rem;
$slider-width-vertical: 0.3125rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.5rem;
$slider-height: 0.3125rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 1.4rem;
$slider-handle-width: 1.4rem;
$slider-handle-height: 0.875rem;
$slider-handle-width: 0.875rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
@@ -569,7 +569,7 @@ $tab-expand-max: 6;
$tab-content-background: transparent;
$tab-content-border: transparent;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 1rem;
$tab-content-padding: 0.625rem;
// 34. Thumbnail
// -------------
@@ -586,11 +586,11 @@ $thumbnail-radius: $global-radius;
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.5rem;
$titlebar-padding: 0.3125rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.25rem;
$titlebar-icon-spacing: 0.15625rem;
// 36. Tooltip
// -----------
@@ -599,19 +599,19 @@ $has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black;
$tooltip-color: $white;
$tooltip-padding: 0.75rem;
$tooltip-padding: 0.46875rem;
$tooltip-font-size: $font-size-mini;
$tooltip-pip-width: 0.75rem;
$tooltip-pip-width: 0.46875rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 37. Top Bar
// -----------
$topbar-padding: 0.5rem;
$topbar-padding: 0.3125;
$topbar-background: $light-gray;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 0.5rem 1rem 0.5rem 0;
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;

View File

@@ -1,5 +1,5 @@
.bg-light {
@include background-light;
@apply bg-slate-25 dark:bg-slate-800;
}
.flex-center {

View File

@@ -22,43 +22,43 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
}
@mixin border-normal() {
border: 1px solid $color-border;
@apply border border-slate-50 dark:border-slate-700;
}
@mixin border-normal-left() {
border-left: 1px solid $color-border;
@apply border-l border-slate-50 dark:border-slate-700;
}
@mixin border-normal-top() {
border-top: 1px solid $color-border;
@apply border-t border-slate-50 dark:border-slate-700;
}
@mixin border-normal-right() {
border-right: 1px solid $color-border;
@apply border-r border-slate-50 dark:border-slate-700;
}
@mixin border-normal-bottom() {
border-bottom: 1px solid $color-border;
@apply border-b border-slate-50 dark:border-slate-700;
}
@mixin border-light() {
border: 1px solid $color-border-light;
@apply border border-slate-25 dark:border-slate-700;
}
@mixin border-light-left() {
border-left: 1px solid $color-border-light;
@apply border-l border-slate-25 dark:border-slate-700;
}
@mixin border-light-top() {
border-top: 1px solid $color-border-light;
@apply border-t border-slate-25 dark:border-slate-700;
}
@mixin border-light-right() {
border-right: 1px solid $color-border-light;
@apply border-r border-slate-25 dark:border-slate-700;
}
@mixin border-light-bottom() {
border-bottom: 1px solid $color-border-light;
@apply border-b border-slate-25 dark:border-slate-700;
}
// background
@@ -67,11 +67,11 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
}
@mixin background-light() {
background: $color-background-light;
@apply bg-slate-50 dark:bg-slate-800;
}
@mixin background-white() {
background: $color-white;
@apply bg-white dark:bg-slate-900;
}
// input form
@@ -237,8 +237,8 @@ $spinner-before-border-color: rgba(255, 255, 255, 0.7);
white-space: nowrap;
}
@mixin three-column-grid($column-one-width: 25.6rem,
$column-three-width: 25.6rem) {
@mixin three-column-grid($column-one-width: 16rem,
$column-three-width: 16rem) {
width: 100%;
height: 100%;
display: grid;

View File

@@ -1,90 +1,6 @@
.app-rtl--wrapper {
direction: rtl;
// Primary sidebar
.primary--sidebar {
border-left: 1px solid var(--s-50);
border-right: 0;
.options-menu.dropdown-pane {
right: var(--space-smaller);
.auto-offline--toggle {
padding: var(--space-smaller) var(--space-one) var(--space-smaller)
var(--space-smaller);
}
.status-items .button {
text-align: right;
}
}
}
// Secondary sidebar
.secondary-sidebar {
.secondary-menu {
border-left: 1px solid var(--s-50);
border-right: 0;
.nested.vertical.menu {
.badge--icon {
margin-left: var(--space-smaller);
margin-right: unset;
}
.menu-label {
text-align: right;
}
}
.secondary-menu--icon {
margin-left: var(--space-smaller);
margin-right: unset;
}
.account-context--group .account-context--switch-group {
--overlay-shadow: linear-gradient(
to left,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
background-image: var(--overlay-shadow);
}
// Help center sidebar
.sidebar-header--wrap .header-title--wrap {
margin-left: unset;
margin-right: var(--space-small);
}
}
}
// Woot button
.button {
.icon--emoji + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
.icon--font + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
.icon + .button__content {
padding-left: 0;
padding-right: var(--space-small);
}
}
// Settings header
.settings-header {
.header--icon {
margin-left: var(--space-small);
margin-right: var(--space-smaller);
}
}
.header-section.back-button {
direction: initial;
margin-left: var(--space-normal);
@@ -133,23 +49,6 @@
// Conversation details
.conversation-details-wrap {
.conv-header {
.user {
margin-left: var(--space-normal);
margin-right: unset;
.user--profile__meta {
margin-left: unset;
margin-right: var(--space-small);
}
}
.actions--container .resolve-actions {
margin-left: unset;
margin-right: var(--space-small);
}
}
.conversation-panel {
// Message text
.text-content {
@@ -197,11 +96,6 @@
}
}
// Conversation sidebar toggle button
.sidebar-toggle--button {
transform: rotate(180deg);
}
// Conversation sidebar close button
.close-button--rtl {
transform: rotate(180deg);
@@ -345,31 +239,6 @@
}
}
// scss-lint:disable SelectorDepth
.container .header-wrap .header-left-wrap .header-left-wrap > .page-title {
margin-right: var(--space-small);
}
.portal-container .container {
margin-left: unset !important;
margin-right: var(--space-small);
.configuration-items--wrap {
margin-left: var(--space-mega);
margin-right: unset !important;
}
thead th {
padding-left: var(--space-small);
padding-right: 0;
}
tbody td {
padding-left: var(--space-small);
padding-right: 0;
}
}
.portal-popover__container .portal {
.actions-container {
margin-left: unset;
@@ -439,7 +308,7 @@
}
span {
--minus-space-one-point-five: -1.5rem;
--minus-space-one-point-five: -0.9375rem;
&.active {
transform: translate(
@@ -473,11 +342,6 @@
direction: initial;
}
.inbox--name .inbox--icon {
margin-left: var(--space-micro);
margin-right: 0;
}
.colorpicker--chrome {
direction: initial;
}

View File

@@ -29,4 +29,5 @@ a {
p {
font-size: $font-size-small;
word-spacing: .12em;
}

View File

@@ -43,17 +43,13 @@
}
.border-right {
border-right: 1px solid var(--color-border);
@apply border-r border-slate-50 dark:border-slate-700;
}
.border-left {
border-left: 1px solid var(--color-border);
}
.bg-white {
background-color: var(--white);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -1,30 +1,30 @@
// Font sizes
$font-size-nano: 0.8rem;
$font-size-micro: 1.0rem;
$font-size-mini: 1.2rem;
$font-size-small: 1.4rem;
$font-size-default: 1.6rem;
$font-size-medium: 1.8rem;
$font-size-large: 2.2rem;
$font-size-big: 2.4rem;
$font-size-bigger: 3.0rem;
$font-size-mega: 3.4rem;
$font-size-giga: 4.0rem;
$font-size-nano: 0.5rem;
$font-size-micro: 0.675rem;
$font-size-mini: 0.75rem;
$font-size-small: 0.875rem;
$font-size-default: 1rem;
$font-size-medium: 1.125rem;
$font-size-large: 1.375rem;
$font-size-big: 1.5rem;
$font-size-bigger: 1.75rem;
$font-size-mega: 2.125rem;
$font-size-giga: 2.5rem;
// spaces
$zero: 0;
$space-micro: 0.2rem;
$space-smaller: 0.4rem;
$space-small: 0.8rem;
$space-one: 1rem;
$space-slab: 1.2rem;
$space-normal: 1.6rem;
$space-two: 2.0rem;
$space-medium: 2.4rem;
$space-large: 3.2rem;
$space-larger: 4.8rem;
$space-jumbo: 6.4rem;
$space-mega: 10.0rem;
$space-micro: 0.125rem;
$space-smaller: 0.25rem;
$space-small: 0.5rem;
$space-one: 0.675rem;
$space-slab: 0.75rem;
$space-normal: 1rem;
$space-two: 1.25rem;
$space-medium: 1.5rem;
$space-large: 2rem;
$space-larger: 3rem;
$space-jumbo: 4rem;
$space-mega: 6.25rem;
// font-weight
$font-weight-feather: 100;
@@ -35,8 +35,8 @@ $font-weight-bold: 600;
$font-weight-black: 700;
//Navbar
$nav-bar-width: 23rem;
$header-height: 5.6rem;
$nav-bar-width: 14.375rem;
$header-height: 3.5rem;
$woot-logo-padding: $space-large $space-two;
@@ -71,20 +71,20 @@ $color-primary-light: #c7e3ff;
$color-primary-dark: darken($color-woot, 20%);
// Thumbnail
$thumbnail-radius: 4rem;
$thumbnail-radius: 2.5rem;
// chat-header
$conv-header-height: 4rem;
$conv-header-height: 2.5rem;
// Inbox List
$inbox-thumb-size: 4.8rem;
$inbox-thumb-size: 3rem;
// Spinner
$spinkit-spinner-color: $color-white !default;
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
$spinkit-size: 1.6rem !default;
$spinkit-spinner-margin: 0 0 0 1rem !default;
$spinkit-size: 1rem !default;
// Snackbar default
$woot-snackbar-bg: #323232;
@@ -101,5 +101,5 @@ $ionicons-font-path: '~ionicons/fonts';
$transition-ease-in: all 0.250s ease-in;
:root {
--dashboard-app-tabs-height: 3.9rem;
--dashboard-app-tabs-height: 2.4375rem;
}

View File

@@ -1,4 +1,8 @@
@import 'shared/assets/fonts/inter';
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import 'shared/assets/fonts/plus-jakarta';
@import 'shared/assets/stylesheets/animations';
@import 'shared/assets/stylesheets/colors';
@import 'shared/assets/stylesheets/spacing';
@@ -67,3 +71,7 @@
@import 'plugins/dropdown';
@import '~shared/assets/stylesheets/ionicons';
@import 'utility-helpers';
.tooltip {
@apply bg-slate-900 text-white py-1 px-2 z-40 text-xs rounded-md dark:bg-slate-200 dark:text-slate-900;
}

View File

@@ -7,6 +7,7 @@
z-index: var(--z-index-very-high);
&.dropdown-pane--open {
@apply bg-white dark:bg-slate-800;
display: block;
visibility: visible;
}

View File

@@ -1,93 +1,94 @@
@mixin label-multiselect-hover {
&::after {
color: $color-primary-dark;
@apply text-woot-600 dark:text-woot-600;
}
&:hover {
background: $color-background;
@apply bg-slate-50 dark:bg-slate-700;
&::after {
color: $color-woot;
@apply text-woot-500 dark:text-woot-500;
}
}
}
.multiselect {
&:not(.no-margin) {
margin-bottom: var(--space-normal);
@apply mb-4;
}
&.multiselect--disabled {
opacity: 0.8;
@apply opacity-50 border border-slate-200 dark:border-slate-600 rounded-md cursor-not-allowed;
.multiselect__select {
@apply cursor-not-allowed bg-white dark:bg-slate-900 rounded-md;
}
.multiselect__tags {
@apply border-0;
}
}
.multiselect--active {
>.multiselect__tags {
border-color: var(--w-500);
> .multiselect__tags {
@apply border-woot-500 dark:border-woot-500;
}
}
.multiselect__select {
min-height: 4.6rem;
padding: 0;
right: 0;
top: 0;
@apply min-h-[2.875rem] p-0 right-0 top-0;
&::before {
right: 0;
@apply right-0;
}
}
.multiselect__content-wrapper {
@apply bg-white dark:bg-slate-900 border border-solid border-slate-200 dark:border-slate-600 text-slate-800 dark:text-slate-100;
}
.multiselect__content {
max-width: 100%;
@apply max-w-full;
.multiselect__option {
font-size: $font-size-small;
font-weight: $font-weight-normal;
@apply text-sm font-normal;
span {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: fit-content;
@apply inline-block overflow-hidden text-ellipsis whitespace-nowrap w-fit;
}
p {
margin-bottom: 0;
@apply mb-0;
}
&.multiselect__option--highlight {
background: var(--white);
color: var(--color-body);
@apply bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100;
}
&.multiselect__option--highlight:hover {
background: var(--w-50);
color: var(--color-body);
@apply bg-woot-50 dark:bg-woot-600 text-slate-800 dark:text-slate-100;
&::after {
background: var(--w-50);
color: var(--s-600);
@apply bg-woot-50 dark:bg-woot-600 text-slate-600 dark:text-slate-200;
}
}
&.multiselect__option--highlight::after {
background: transparent;
@apply bg-transparent;
}
&.multiselect__option--selected {
background: var(--w-75);
@apply bg-woot-50 dark:bg-woot-600 text-slate-800 dark:text-slate-100;
&.multiselect__option--highlight:hover {
background: var(--w-75);
@apply bg-woot-75 dark:bg-woot-600;
&::after {
background: transparent;
@apply bg-transparent;
}
&::after:hover {
color: var(--color-body);
@apply text-slate-800 dark:text-slate-100;
}
}
}
@@ -95,175 +96,130 @@
}
.multiselect__tags {
border: 1px solid var(--s-200);
border-color: var(--s-200);
margin: 0;
min-height: 4.4rem;
padding-top: $zero;
@apply bg-white dark:bg-slate-900 border border-solid border-slate-200 dark:border-slate-600 m-0 min-h-[2.875rem] pt-0;
}
.multiselect__tags-wrap {
display: inline-block;
line-height: 1;
margin-top: $space-smaller;
@apply inline-block leading-none mt-1;
}
.multiselect__placeholder {
color: $color-gray;
font-weight: $font-weight-normal;
padding-top: var(--space-slab);
@apply text-slate-400 dark:text-slate-400 font-normal pt-3;
}
.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;
@apply bg-slate-50 dark:bg-slate-800 mt-1 text-slate-800 dark:text-slate-100 pr-6 pl-2.5 py-1.5;
}
.multiselect__tag-icon {
@include label-multiselect-hover;
line-height: $space-medium + $space-micro;
}
.multiselect__input {
@include ghost-input;
font-size: $font-size-small;
height: 4.4rem;
margin-bottom: $zero;
padding: 0;
@apply text-sm h-[2.875rem] mb-0 p-0;
}
.multiselect__single {
@include text-ellipsis;
display: inline-block;
margin-bottom: 0;
padding: var(--space-slab) var(--space-one);
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 inline-block mb-0 py-3 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis;
}
}
.sidebar-labels-wrap {
&.has-edited,
&:hover {
.multiselect {
cursor: pointer;
@apply cursor-pointer;
}
}
.multiselect {
>.multiselect__select {
visibility: hidden;
> .multiselect__select {
@apply invisible;
}
>.multiselect__tags {
border-color: transparent;
> .multiselect__tags {
@apply border-transparent;
}
&.multiselect--active>.multiselect__tags {
border-color: $color-woot;
&.multiselect--active > .multiselect__tags {
@apply border-woot-500 dark:border-woot-500;
}
}
}
.multiselect-wrap--small {
$multiselect-height: 4.0rem;
.multiselect__tags,
.multiselect__input {
align-items: center;
display: flex;
@apply items-center flex;
}
.multiselect__tags,
.multiselect__input,
.multiselect {
background: var(--white);
font-size: var(--font-size-small);
height: $multiselect-height;
min-height: $multiselect-height;
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 rounded-[5px] text-sm h-10 min-h-[2.5rem];
}
.multiselect__input {
height: $multiselect-height - $space-micro;
min-height: $multiselect-height - $space-micro;
@apply h-[2.375rem] min-h-[2.375rem];
}
.multiselect__single {
align-items: center;
display: flex;
font-size: var(--font-size-small);
margin: 0;
max-height: 3.8rem;
padding: var(--space-smaller) var(--space-micro);
@apply items-center flex m-0 text-sm max-h-[2.375rem] text-slate-800 dark:text-slate-100 bg-white dark:bg-slate-900 py-1 px-0.5;
}
.multiselect__placeholder {
margin: 0;
padding: var(--space-smaller) var(--space-micro);
@apply m-0 py-1 px-0.5;
}
.multiselect__select {
min-height: $multiselect-height;
@apply min-h-[2.5rem];
}
.multiselect--disabled .multiselect__current,
.multiselect--disabled .multiselect__select {
background: transparent;
@apply bg-transparent;
}
.multiselect__tags-wrap {
flex-shrink: 0;
@apply flex-shrink-0;
}
}
.multiselect-wrap--medium {
$multiselect-height: 4.8rem;
.multiselect__tags,
.multiselect__input {
align-items: center;
display: flex;
@apply items-center flex;
}
.multiselect__tags,
.multiselect__input,
.multiselect {
background: var(--white);
font-size: var(--font-size-small);
height: $multiselect-height;
min-height: $multiselect-height;
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 text-sm h-12 min-h-[3rem];
}
.multiselect__input {
height: $multiselect-height - $space-micro;
min-height: $multiselect-height - $space-micro;
@apply h-[2.875rem] min-h-[2.875rem];
}
.multiselect__single {
align-items: center;
display: flex;
font-size: var(--font-size-small);
margin: 0;
padding: var(--space-smaller) var(--space-micro);
@apply items-center flex m-0 text-sm py-1 px-0.5 text-slate-800 dark:text-slate-100 bg-white dark:bg-slate-900;
}
.multiselect__placeholder {
margin: 0;
padding: var(--space-smaller) var(--space-micro);
@apply m-0 py-1 px-0.5;
}
.multiselect__select {
min-height: $multiselect-height;
@apply min-h-[3rem];
}
.multiselect--disabled .multiselect__current,
.multiselect--disabled .multiselect__select {
background: transparent;
@apply bg-transparent;
}
.multiselect__tags-wrap {
flex-shrink: 0;
@apply flex-shrink-0;
}
}

View File

@@ -1,4 +1,5 @@
@import 'shared/assets/fonts/inter';
@import 'shared/assets/fonts/plus-jakarta';
@import 'shared/assets/stylesheets/animations';
@import 'shared/assets/stylesheets/colors';
@import 'shared/assets/stylesheets/spacing';
@@ -9,7 +10,7 @@
@import 'variables';
@import '~spinkit/scss/spinners/7-three-bounce';
@import '~vue-multiselect/dist/vue-multiselect.min.css';
@import 'vue-multiselect/dist/vue-multiselect.min.css';
@import '~shared/assets/stylesheets/ionicons';
@import 'mixins';
@@ -29,3 +30,18 @@
@import 'widgets/forms';
@import 'plugins/multiselect';
@import 'widget/assets/scss/reset';
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import 'widget/assets/scss/utilities';
html,
body {
font-family: 'PlusJakarta', sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
height: 100%;
}

View File

@@ -1,3 +1,3 @@
@import 'shared/assets/fonts/inter';
@import 'shared/assets/fonts/plus-jakarta';
@import '../variables';
@import '~shared/assets/stylesheets/ionicons';

View File

@@ -1,173 +1,112 @@
.settings {
overflow: auto;
}
// Conversation header - Light BG
.settings-header {
@include background-white;
@include flex;
@include flex-align($x: justify, $y: middle);
border-bottom: 1px solid var(--s-50);
height: $header-height;
min-height: $header-height;
padding: $space-small $space-normal;
// Resolve Button
.button {
margin: 0;
}
// User thumbnail and text
.page-title {
@include flex;
@include flex-align($x: center, $y: middle);
margin: 0;
}
@apply overflow-auto;
}
.wizard-box {
.item {
@include background-light;
cursor: pointer;
padding: $space-normal $space-normal $space-normal $space-medium;
position: relative;
@apply cursor-pointer py-4 pr-4 pl-6 relative;
&::before,
&::after {
background: $color-border;
content: '';
height: 100%;
position: absolute;
top: $space-normal;
width: 2px;
@apply bg-slate-75 dark:bg-slate-600 content-[''] h-full absolute top-5 w-0.5;
}
&::before {
height: $space-normal;
top: $zero;
@apply h-4 top-0;
}
&:first-child {
&::before {
height: 0;
@apply h-0;
}
}
&:last-child {
&::after {
height: $zero;
@apply h-0;
}
}
&.active {
h3 {
color: $color-woot;
@apply text-woot-500 dark:text-woot-500;
}
.step {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
}
&.over {
&::after {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
.step {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
& + .item {
&::before {
background: $color-woot;
@apply bg-woot-500 dark:bg-woot-500;
}
}
}
h3 {
color: $color-body;
font-size: $font-size-default;
line-height: 1;
padding-left: $space-medium;
@apply text-slate-800 dark:text-slate-100 text-base pl-6;
}
.completed {
color: $success-color;
margin-left: $space-smaller;
@apply text-green-500 dark:text-green-500 ml-1;
}
p {
color: $color-light-gray;
font-size: $font-size-small;
margin: 0;
padding-left: $space-medium;
@apply text-slate-600 dark:text-slate-300 text-sm m-0 pl-6;
}
.step {
background: $color-border;
border-radius: 20px;
color: $color-white;
font-size: $font-size-micro;
font-weight: $font-weight-medium;
height: $space-normal;
left: $space-normal;
line-height: $space-normal;
position: absolute;
text-align: center;
top: $space-normal;
width: $space-normal;
z-index: 999;
@apply bg-slate-75 dark:bg-slate-600 rounded-2xl font-medium w-4 left-4 leading-4 z-[999] absolute text-center text-white dark:text-white text-xxs top-5;
i {
font-size: $font-size-micro;
@apply text-xxs;
}
}
}
}
.wizard-body {
@include background-white;
@include border-light;
@include full-height();
padding: $space-medium;
@apply border border-slate-25 dark:border-slate-800/60 bg-white dark:bg-slate-900 h-full p-6;
&.height-auto {
height: auto;
@apply h-auto;
}
}
.settings--content {
margin: $space-small $space-large;
@apply my-2 mx-8;
.title {
font-weight: $font-weight-medium;
@apply font-medium;
}
.code {
background: $color-background;
max-height: $space-mega;
overflow: auto;
padding: $space-one;
white-space: nowrap;
@apply bg-slate-50 dark:bg-slate-800 overflow-auto p-2.5 whitespace-nowrap;
code {
background: transparent;
border: 0;
@apply bg-transparent border-0;
}
}
}
.login-init {
padding-top: 30%;
text-align: center;
@apply pt-[30%] text-center;
p {
padding: $space-medium;
@apply p-6;
}
> a > img {
width: $space-larger * 5;
@apply w-60;
}
}

View File

@@ -1,41 +1 @@
.integrations-wrap {
.integration {
background: $color-white;
border: 1px solid $color-border;
border-radius: $space-smaller;
margin-bottom: $space-normal;
padding: $space-normal;
.integration--image {
display: flex;
height: 10rem;
width: 10rem;
img {
max-width: 100%;
padding: $space-medium;
}
}
.integration--type {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 var(--space-normal);
}
.integration--title {
font-size: var(--font-size-large);
}
.button-wrap {
@include flex;
@include flex-align(center, middle);
margin-bottom: 0;
}
}
}
.help-wrap {
padding-left: $space-large;
}
// to be removed

View File

@@ -1,207 +1,201 @@
$default-button-height: 4.0rem;
.button {
align-items: center;
display: inline-flex;
height: $default-button-height;
margin-bottom: 0;
@apply items-center inline-flex h-10 mb-0;
.button__content {
width: 100%;
@apply w-full;
img,
svg {
@apply inline-block;
}
}
.spinner {
padding: 0 var(--space-small);
@apply px-2 py-0;
}
.icon--emoji+.button__content {
padding-left: var(--space-small);
.icon--emoji + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
.icon--font+.button__content {
padding-left: var(--space-small);
.icon--font + .button__content {
@apply pl-2 rtl:pr-2 rtl:pl-0;
}
// @TODDO - Remove after moving all buttons to woot-button
.icon+.button__content {
padding-left: var(--space-small);
width: auto;
.icon + .button__content {
@apply pl-2 w-auto rtl:pr-2 rtl:pl-0;
}
&.expanded {
display: flex;
justify-content: center;
@apply flex justify-center text-center;
}
&.round {
border-radius: $space-larger;
@apply rounded-full;
}
// @TODO Use with link
&.compact {
padding-bottom: 0;
padding-top: 0;
@apply pb-0 pt-0;
}
&.hollow {
border-color: var(--s-200);
color: var(--w-700);
@apply border border-slate-200 dark:border-slate-600 text-woot-700 dark:text-woot-100 hover:bg-woot-50 dark:hover:bg-woot-900;
&.secondary {
border-color: var(--s-200);
color: var(--s-700);
@apply text-slate-700 border-slate-200 dark:border-slate-600 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
}
&.success {
border-color: var(--s-200);
color: var(--g-700);
@apply text-green-700 dark:text-green-100 hover:bg-green-50 dark:hover:bg-green-800;
}
&.alert {
border-color: var(--s-200);
color: var(--r-700);
@apply text-red-700 dark:text-red-100 hover:bg-red-50 dark:hover:bg-red-800;
}
&.warning {
border-color: var(--s-200);
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-100 hover:bg-yellow-50 dark:hover:bg-yellow-800;
}
&:hover {
background: var(--s-75);
border-color: var(--s-100);
@apply bg-slate-75 dark:bg-slate-900 border-slate-100 dark:border-slate-700;
&.secondary {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
}
&.success {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-green-800 dark:text-green-100;
}
&.alert {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-red-700 dark:text-red-100;
}
&.warning {
border-color: var(--s-100);
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-700;
}
}
}
// Smooth style
&.smooth {
@include button-style(var(--w-50), var(--w-100), var(--w-700));
@apply bg-woot-50 dark:bg-woot-800 text-woot-700 dark:text-woot-100 hover:text-woot-700 dark:hover:text-woot-700 hover:bg-woot-100 dark:hover:bg-woot-900;
&.secondary {
@include button-style(var(--s-50), var(--s-100), var(--s-700));
@apply bg-slate-50 dark:bg-slate-700 text-slate-700 dark:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800;
}
&.success {
@include button-style(var(--g-50), var(--g-100), var(--g-700));
@apply bg-green-50 dark:bg-green-700 text-green-700 dark:text-green-100 hover:bg-green-100 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100;
}
&.alert {
@include button-style(var(--r-50), var(--r-100), var(--r-700));
@apply bg-red-50 dark:bg-red-700 dark:bg-opacity-50 text-red-700 dark:text-red-100 hover:bg-red-100 dark:hover:bg-red-800 dark:hover:bg-opacity-30;
}
&.warning {
@include button-style(var(--y-100), var(--y-200), var(--y-700));
@apply bg-yellow-100 dark:bg-yellow-100 text-yellow-700 dark:text-yellow-700 hover:bg-yellow-200 dark:hover:bg-yellow-200;
}
}
&.clear {
color: var(--w-700);
@apply text-woot-500 dark:text-woot-500;
&.secondary {
color: var(--s-700);
@apply text-slate-700 dark:text-slate-100;
}
&.success {
color: var(--g-700);
@apply text-green-700 dark:text-green-100;
}
&.alert {
color: var(--r-700);
@apply text-red-700 dark:text-red-100;
}
&.warning {
color: var(--y-700);
@apply text-yellow-700 dark:text-yellow-600;
}
&:hover {
background: var(--w-50);
@apply hover:bg-woot-50 dark:hover:bg-woot-900/50 hover:text-woot-500 dark:hover:text-woot-100;
&.secondary {
background: var(--s-50);
@apply hover:bg-slate-50 dark:hover:bg-slate-700 hover:text-slate-800 dark:hover:text-slate-100;
}
&.success {
background: var(--g-50);
@apply hover:bg-green-50 dark:hover:bg-green-800 hover:text-green-800 dark:hover:text-green-100;
}
&.alert {
background: var(--r-50);
@apply hover:bg-red-50 dark:hover:bg-red-800 hover:text-red-700 dark:hover:text-red-100;
}
&.warning {
background: var(--y-50);
@apply hover:bg-yellow-100 dark:hover:bg-yellow-800 hover:text-yellow-700 dark:hover:text-yellow-600;
}
}
&:active {
&.secondary {
@apply active:bg-slate-100 dark:active:bg-slate-900;
}
}
&:focus {
&.secondary {
@apply focus:bg-slate-50 dark:focus:bg-slate-700;
}
}
}
// Sizes
&.tiny {
height: var(--space-medium);
@apply h-6;
.icon+.button__content {
padding-left: var(--space-micro);
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.small {
height: var(--space-large);
padding-bottom: var(--space-smaller);
padding-top: var(--space-smaller);
@apply h-8 pb-1 pt-1;
.icon+.button__content {
padding-left: var(--space-smaller);
.icon + .button__content {
@apply pl-1 rtl:pr-1 rtl:pl-0;
}
}
&.large {
height: var(--space-larger);
@apply h-12;
}
&.button--only-icon {
justify-content: center;
padding-left: 0;
padding-right: 0;
width: $default-button-height;
@apply justify-center pl-0 pr-0 w-10;
&.tiny {
width: var(--space-medium);
@apply w-6;
}
&.small {
width: var(--space-large);
@apply w-8;
}
&.large {
width: var(--space-larger);
@apply w-12;
}
}
&.link {
height: auto;
margin: 0;
padding: 0;
@apply h-auto m-0 p-0;
&:hover {
text-decoration: underline;
@apply underline;
}
}
}

View File

@@ -1,71 +1 @@
$resolve-button-width: 13.2rem;
// Conversation header - Light BG
.conv-header {
@include background-white;
@include flex;
@include flex-align($x: justify, $y: middle);
@include border-normal-bottom;
padding: var(--space-small) var(--space-normal);
.multiselect-box {
@include flex;
@include flex-align($x: justify, $y: middle);
border: 1px solid var(--color-border);
border-radius: var(--space-smaller);
margin-right: var(--space-small);
width: 21.6rem;
.icon {
color: $medium-gray;
font-size: $font-size-default;
line-height: 3.8rem;
padding-left: $space-slab;
padding-right: $space-smaller;
}
.multiselect {
border-radius: var(--border-radius-small);
margin: 0;
min-width: 0;
.multiselect__tags {
border-color: transparent;
}
}
}
// User thumbnail and text
.user {
@include flex;
@include flex-align($x: center, $y: middle);
margin-right: var(--space-normal);
min-width: 0;
.user--profile__meta {
align-items: flex-start;
display: flex;
flex-direction: column;
justify-content: flex-start;
margin-left: var(--space-small);
min-width: 0;
}
}
}
.header-actions-wrap {
align-items: center;
display: flex;
flex-direction: row;
flex-grow: 1;
justify-content: flex-end;
margin-top: var(--space-small);
@include breakpoint(medium up) {
margin-top: 0;
}
&.has-open-sidebar {
justify-content: flex-end;
}
}
// File to be removed

View File

@@ -10,135 +10,7 @@
}
.conversation {
@include flex;
@include flex-shrink;
border-bottom: 1px solid transparent;
border-left: var(--space-micro) solid transparent;
border-top: 1px solid transparent;
cursor: pointer;
padding: 0 var(--space-normal);
position: relative;
&.active {
animation: left-shift-animation 0.25s $swift-ease-out-function;
background: var(--color-background);
border-bottom-color: var(--color-border-light);
border-left-color: var(--color-woot);
border-top-color: var(--color-border-light);
.conversation--details {
border-top-color: transparent;
}
+ .conversation .conversation--details {
border-top-color: transparent;
}
}
&:first-child {
.conversation--details {
border-top-color: transparent;
}
}
&:last-child {
.conversation--details {
border-bottom-color: var(--color-border-light);
}
}
.conversation--details {
@include border-light-bottom;
@include border-light-top;
border-bottom-color: transparent;
padding: var(--space-slab) 0;
}
.conversation--user {
font-size: var(--font-size-small);
margin: 0 var(--space-small);
text-transform: capitalize;
.label {
left: var(--space-one);
max-width: var(--space-jumbo);
overflow: hidden;
position: relative;
text-overflow: ellipsis;
top: var(--space-one);
white-space: nowrap;
}
}
.conversation--message {
color: var(--color-body);
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
height: var(--space-medium);
line-height: var(--space-medium);
margin: 0 var(--space-small);
max-width: 96%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 27rem;
}
.conversation--meta {
@include flex;
flex-direction: column;
position: absolute;
right: var(--space-normal);
top: var(--space-normal);
.unread {
@include round-corner;
@include light-shadow;
background: darken($success-color, 3%);
color: var(--white);
display: none;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-black);
height: var(--space-normal);
line-height: var(--space-normal);
margin-left: auto;
margin-top: var(--space-smaller);
min-width: var(--space-normal);
padding: 0 var(--space-smaller);
text-align: center;
}
.timestamp {
color: $dark-gray;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-normal);
line-height: var(--space-normal);
margin-left: auto;
}
}
&.unread-chat {
.unread {
display: inline-block;
}
.conversation--message {
font-weight: var(--font-weight-bold);
}
.conversation--user {
font-weight: var(--font-weight-bold);
}
}
&.compact {
padding-left: 0;
.conversation--details {
border-radius: var(--border-radius-small);
margin-left: 0;
padding-left: var(--space-two);
padding-right: var(--space-small);
}
}
}

View File

@@ -1,352 +1,229 @@
// scss-lint:disable MergeableSelector
@mixin bubble-with-types {
padding: $space-small $space-normal;
margin: 0;
background: $color-woot;
border-radius: $space-one;
color: var(--white);
font-size: $font-size-small;
font-weight: $font-weight-normal;
position: relative;
.message-text__wrap {
position: relative;
.link {
color: var(--white);
text-decoration: underline;
}
@tailwind utilities;
@layer utilities {
.custom-gradient {
background-image: linear-gradient(
-180deg,
transparent 3%,
rgb(76 81 85) 130%
);
}
.image,
.video {
cursor: pointer;
position: relative;
.bubble-with-types {
@apply py-2 text-sm font-normal bg-woot-500 dark:bg-woot-500 relative px-4 m-0 text-white dark:text-white;
.modal-container {
text-align: center;
.message-text__wrap {
@apply relative;
.link {
@apply text-white dark:text-white underline;
}
}
.modal-image {
max-height: 80vh;
max-width: 80vw;
.image,
.video {
@apply cursor-pointer relative;
.modal-container {
@apply text-center;
}
.modal-image {
@apply max-h-[76vh] max-w-[76vw];
}
.modal-video {
@apply max-h-[76vh] max-w-[76vw];
}
&::before {
@apply custom-gradient bottom-0 h-[20%] content-[''] left-0 absolute w-full opacity-80;
}
}
.modal-video {
max-height: 80vh;
max-width: 80vw;
}
&::before {
background-image: linear-gradient(
-180deg,
transparent 3%,
$color-heading 130%
);
bottom: 0;
content: '';
height: 20%;
left: 0;
opacity: 0.8;
position: absolute;
width: 100%;
}
}
}
.conversations-list-wrap {
@include flex;
border-right: 1px solid var(--s-50);
flex-direction: column;
.load-more-conversations {
font-size: $font-size-small;
margin: 0;
padding: $space-normal;
width: 100%;
}
.end-of-list-text {
padding: $space-normal;
}
.conversations-list {
flex: 1 1;
overflow-y: auto;
@include breakpoint(large up) {
@include scroll-on-hover;
}
}
.chat-list__top {
@include flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--space-normal);
}
.content-box {
text-align: center;
}
}
.conversation-panel {
@include flex;
flex: 1 1 1px;
flex-direction: column;
height: 100%;
margin: 0;
overflow-y: auto;
padding-bottom: var(--space-normal);
position: relative;
@apply flex-shrink flex-grow basis-px flex flex-col overflow-y-auto relative h-full m-0 pb-4;
}
.conversation-panel > li {
@include flex;
@include flex-shrink;
margin: $zero $zero $space-micro;
position: relative;
&:first-child {
margin-top: auto;
}
&:last-child {
margin-bottom: 0;
}
@apply flex flex-shrink-0 flex-grow-0 flex-auto max-w-full mt-0 mr-0 mb-1 ml-0 relative first:mt-auto last:mb-0;
&.unread--toast {
+ .right {
margin-bottom: var(--space-micro);
@apply mb-1;
}
+ .left {
margin-bottom: 0;
@apply mb-0;
}
span {
@include elegant-card;
@include round-corner;
background: $color-woot;
color: var(--white);
font-size: $font-size-mini;
font-weight: $font-weight-medium;
margin: $space-one auto;
padding: $space-smaller $space-two;
@apply shadow-lg rounded-full bg-woot-500 dark:bg-woot-500 text-white dark:text-white text-xs font-medium my-2.5 mx-auto px-2.5 py-1.5;
}
}
.bubble {
@include bubble-with-types;
text-align: left;
word-wrap: break-word;
@apply bubble-with-types text-left break-words;
.aplayer {
box-shadow: none;
@apply shadow-none;
font-family: inherit;
}
}
&.left {
.bubble {
@include border-normal;
background: $white;
border-bottom-left-radius: $space-smaller;
border-top-left-radius: $space-smaller;
color: $color-body;
margin-right: auto;
word-break: break-word;
@apply border border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-700 text-black-900 dark:text-slate-50 rounded-r-lg rounded-l mr-auto break-words;
&.is-image {
border-radius: var(--border-radius-large);
@apply rounded-lg;
}
.link {
color: $color-primary-dark;
@apply text-woot-600 dark:text-woot-600;
}
.file {
.text-block-title {
color: $color-body;
@apply text-slate-700 dark:text-woot-300;
}
.icon-wrap {
color: $color-woot;
@apply text-woot-600 dark:text-woot-600;
}
.download {
color: $color-primary-dark;
@apply text-woot-600 dark:text-woot-600;
}
}
}
+ .right {
margin-top: $space-one;
@apply mt-2.5;
.bubble {
border-top-right-radius: $space-one;
@apply rounded-tr-lg;
}
}
+ .unread--toast {
+ .right {
margin-top: $space-one;
@apply mt-2.5;
.bubble {
border-top-right-radius: $space-one;
@apply rounded-tr-lg;
}
}
+ .left {
margin-top: 0;
@apply mt-0;
}
}
}
&.right {
@include flex-align(right, null);
@apply justify-end;
.wrap {
align-items: flex-end;
display: flex;
margin-right: $space-normal;
text-align: right;
@apply flex items-end mr-4 text-right;
.sender--info {
padding: var(--space-small) 0 var(--space-smaller) var(--space-small);
@apply pt-2 pb-1 pr-0 pl-2;
}
}
.bubble {
border-bottom-right-radius: $space-smaller;
border-top-right-radius: $space-smaller;
margin-left: auto;
word-break: break-word;
@apply ml-auto break-words rounded-l-lg rounded-r;
&.is-private {
background: lighten($warning-color, 32%);
border: 1px solid lighten($warning-color, 15%);
color: $color-heading;
position: relative;
@apply text-black-900 dark:text-white relative border border-solid bg-yellow-100 dark:bg-yellow-700 border-yellow-200 dark:border-yellow-600/25;
&::before {
bottom: 0;
color: $medium-gray;
position: absolute;
right: $space-one;
top: $space-smaller + $space-micro;
blockquote {
@apply border-slate-400 dark:border-slate-400 text-slate-800 dark:text-slate-300;
p {
@apply text-slate-600 dark:text-slate-300;
}
}
}
&.is-image {
border-radius: var(--border-radius-large);
@apply rounded-lg;
}
}
+ .left {
margin-top: $space-one;
@apply mt-2.5;
.bubble {
border-top-left-radius: $space-one;
@apply rounded-tl-lg;
}
}
+ .unread--toast {
+ .left {
margin-top: $space-one;
@apply rounded-lg;
.bubble {
border-top-left-radius: $space-one;
@apply rounded-tl-lg;
}
}
+ .right {
margin-top: 0;
@apply mt-0;
}
}
}
&.center {
justify-content: center;
@apply items-center justify-center;
}
.wrap {
--bubble-max-width: 49.6rem;
margin: $zero $space-normal;
max-width: Min(var(--bubble-max-width), 84%);
max-width: Min(31rem, 84%);
@apply my-0 mx-4;
.sender--name {
font-size: $font-size-mini;
margin-bottom: $space-smaller;
@apply text-xs mb-1;
}
}
.sender--thumbnail {
@include round-corner();
height: $space-slab;
margin-right: $space-one;
margin-top: $space-micro;
width: $space-slab;
@apply h-3 mr-3 mt-0.5 w-3 rounded-full;
}
.activity-wrap {
background: var(--s-50);
border: 1px solid var(--s-100);
border-radius: var(--border-radius-medium);
display: flex;
font-size: var(--font-size-small);
justify-content: center;
margin: var(--space-smaller) 0;
padding: var(--space-smaller) var(--space-micro) var(--space-smaller)
var(--space-one);
@apply flex justify-center text-sm my-1 mx-0 py-1 pr-0.5 pl-2.5 bg-slate-50 dark:bg-slate-600 text-slate-800 dark:text-slate-100 rounded-md border border-slate-100 dark:border-slate-600 border-solid;
.is-text {
align-items: center;
display: inline-flex;
text-align: start;
@include breakpoint(xxxlarge up) {
display: flex;
}
@apply inline-flex items-center text-start 2xl:flex;
}
}
}
.activity-wrap .message-text__wrap {
.text-content p {
margin-bottom: 0;
@apply mb-0;
}
}
.conversation-footer {
display: flex;
flex-direction: column;
position: relative;
@apply flex relative flex-col;
}
.typing-indicator-wrap {
align-items: center;
display: flex;
height: 0;
position: absolute;
top: -$space-large;
width: 100%;
@apply items-center flex h-0 absolute w-full -top-8;
.typing-indicator {
@include elegant-card;
@include round-corner;
background: var(--white);
color: $color-light-gray;
font-size: $font-size-mini;
font-weight: $font-weight-bold;
margin: $space-one auto;
padding: $space-small $space-normal $space-small $space-two;
@apply py-2 pr-4 pl-5 bg-white dark:bg-slate-700 text-slate-800 dark:text-slate-100 text-xs font-semibold my-2.5 mx-auto;
.gif {
margin-left: $space-small;
width: $space-medium;
@apply ml-2 w-6;
}
}
}
@@ -358,16 +235,15 @@
h4,
h5,
h6 {
color: var(--color-body);
@apply text-slate-800 dark:text-slate-100;
}
a {
color: var(--color-woot);
text-decoration: underline;
@apply text-woot-500 dark:text-woot-500 underline;
}
p:last-child {
margin-bottom: 0;
@apply mb-0;
}
}
@@ -378,15 +254,14 @@
h4,
h5,
h6 {
color: var(--white);
@apply text-white dark:text-white;
}
a {
color: var(--white);
text-decoration: underline;
@apply text-white dark:text-white underline;
}
p:last-child {
margin-bottom: 0;
@apply mb-0;
}
}

View File

@@ -1,5 +1,29 @@
// scss-lint:disable QualifyingElement
label {
@apply text-slate-800 dark:text-slate-200;
}
textarea {
@apply bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-200 dark:border-slate-600;
}
input {
@apply bg-white dark:bg-slate-900 focus:bg-white focus:dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-200 dark:border-slate-600;
&[disabled] {
@apply bg-slate-200 dark:bg-slate-700 text-slate-400 dark:text-slate-400 border-slate-200 dark:border-slate-600;
}
}
input[type='file'] {
@apply bg-white dark:bg-slate-800;
}
select {
@apply bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-200 dark:border-slate-600;
}
.error {
input[type='color'],
input[type='date'],
@@ -19,17 +43,11 @@
textarea,
select,
.multiselect > .multiselect__tags {
@include thin-border(var(--r-400));
@apply border border-solid border-red-400 dark:border-red-400;
}
.message {
color: var(--r-400);
display: block;
font-size: var(--font-size-small);
font-weight: $font-weight-normal;
margin-bottom: $space-one;
margin-top: -$space-normal;
width: 100%;
@apply text-red-400 dark:text-red-400 block text-sm mb-2.5 w-full;
}
}
@@ -42,22 +60,19 @@ input {
}
.input-wrap {
color: $color-heading;
font-size: $font-size-small;
font-weight: $font-weight-medium;
@apply text-slate-800 dark:text-slate-100 text-sm font-medium;
}
.help-text {
font-weight: $font-weight-normal;
@apply font-normal text-slate-600 dark:text-slate-400;
}
.input-group.small {
input {
font-size: var(--font-size-small);
height: var(--space-large);
@apply text-sm h-8;
}
.error {
border-color: var(--r-400);
@apply border-red-400 dark:border-red-400;
}
}

View File

@@ -1,117 +1,79 @@
@import '~dashboard/assets/scss/variables';
@import '~dashboard/assets/scss/mixins';
.modal-mask {
@include flex;
@include flex-align(center, middle);
background-color: $masked-bg;
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 9990;
// @include flex;
// @include flex-align(center, middle);
@apply flex items-center justify-center bg-modal dark:bg-modal z-[9990] h-full left-0 fixed top-0 w-full;
}
.modal--close {
position: absolute;
right: $space-small;
top: $space-small;
&:hover {
background: $color-background;
}
@apply absolute right-2 rtl:right-[unset] rtl:left-2 top-2;
}
.page-top-bar {
padding: $space-large $space-large $zero;
@apply px-8 pt-9 pb-0;
img {
max-height: 6rem;
@apply max-h-[3.75rem];
}
}
.modal-container {
@include normal-shadow;
background-color: $color-white;
border-radius: $space-smaller;
max-height: 100%;
overflow: auto;
position: relative;
width: 60rem;
@apply shadow-md rounded-sm max-h-full overflow-auto relative w-[37.5rem];
&.medium {
max-width: 80%;
width: 90rem;
@apply max-w-[80%] w-[56.25rem];
}
.content-box {
height: auto;
padding: 0;
@apply h-auto p-0;
}
h2 {
color: $color-heading;
font-size: $font-size-medium;
font-weight: $font-weight-bold;
@apply text-slate-800 dark:text-slate-100 text-lg font-semibold;
}
p {
font-size: $font-size-small;
margin: 0;
padding: 0;
@apply text-sm m-0 p-0 text-slate-600 mt-2 text-sm dark:text-slate-300;
}
.content {
padding: $space-large;
@apply p-8;
}
form,
.modal-content {
align-self: center;
padding: $space-large;
@apply pt-4 pb-8 px-8 self-center;
a {
padding: $space-normal;
@apply p-4;
}
}
.modal-footer {
@include flex;
@include flex-align($x: flex-end, $y: middle);
padding: $space-small $zero;
.button {
margin-left: var(--space-small);
}
&:first-child {
.button {
margin-left: 0;
}
}
// @include flex;
// @include flex-align($x: flex-end, $y: middle);
@apply flex justify-end items-center py-2 px-0 gap-2;
&.justify-content-end {
justify-content: end;
@apply justify-end;
}
}
.delete-item {
padding: $space-large;
@apply p-8;
button {
margin: 0;
@apply m-0;
}
}
}
.modal-enter,
.modal-leave {
opacity: 0;
@apply opacity-0;
}
.modal-enter .modal-container,
.modal-leave .modal-container {
transform: scale(1.1);
// @apply transform scale-110;
}

View File

@@ -29,7 +29,7 @@
// Override min-height : 50px in foundation
//
max-height: $space-mega * 2.4;
min-height: 4.8rem;
min-height: 3rem;
padding: var(--space-normal) 0 0;
resize: none;
}
@@ -40,20 +40,20 @@
margin: 0;
max-height: $space-mega * 2.4;
// Override min-height : 50px in foundation
min-height: 4.8rem;
min-height: 3rem;
padding: var(--space-normal) 0 0;
resize: none;
}
}
&.is-private {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
.reply-box__top {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
> input {
background: var(--y-50);
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
}

View File

@@ -58,31 +58,3 @@
text-transform: capitalize;
}
}
.report-bar {
@include background-white;
@include border-light;
margin: var(--space-minus-micro) 0;
padding: var(--space-small) var(--space-medium);
.chart-container {
@include flex;
@include flex-align(center, middle);
flex-direction: column;
div {
width: 100%;
}
.empty-state {
color: $color-gray;
font-size: var(--font-size-default);
margin: var(--space-jumbo);
}
.business-hours {
margin: var(--space-normal);
text-align: center;
}
}
}

View File

@@ -22,18 +22,6 @@
margin: 0 var(--space-small);
}
.business-hours {
align-items: center;
display: flex;
justify-content: flex-start;
margin-left: auto;
padding-right: var(--space-normal);
}
.business-hours-text {
font-size: var(--font-size-small);
margin: 0 var(--space-small);
}
.switch {
margin-bottom: var(--space-zero);

View File

@@ -47,7 +47,7 @@
}
.dropdown-pane {
bottom: 6rem;
bottom: 3.75rem;
display: block;
visibility: visible;
width: fit-content;

View File

@@ -1,7 +1,7 @@
.ui-snackbar-container {
left: 0;
margin: 0 auto;
max-width: 40rem;
max-width: 25rem;
overflow: hidden;
position: absolute;
right: 0;
@@ -16,9 +16,9 @@
border-radius: $space-smaller;
display: inline-flex;
margin-bottom: $space-small;
max-width: 40rem;
min-height: 3rem;
min-width: 24rem;
max-width: 25rem;
min-height: 1.875rem;
min-width: 15rem;
padding: $space-slab $space-medium;
text-align: left;
}
@@ -31,7 +31,7 @@
.ui-snackbar-action {
margin-left: auto;
padding-left: 3rem;
padding-left: 1.875rem;
button {
background: none;

View File

@@ -1,42 +1 @@
.loading-state {
padding: $space-jumbo $space-smaller;
.message {
color: $color-gray;
display: block;
text-align: center;
width: 100%;
}
.spinner {
float: none;
top: -$space-smaller;
}
}
// EMPTY STATES
.empty-state {
padding: $space-jumbo $space-smaller;
.title,
.message {
display: block;
text-align: center;
width: 100%;
}
.title {
font-size: $font-size-giga;
font-weight: $font-weight-feather;
}
.message {
color: $color-gray;
margin: $space-normal auto;
width: 90%;
}
.button {
margin-top: $space-medium;
}
}
// To be removed

View File

@@ -1,100 +1,69 @@
.tabs--container {
display: flex;
@apply flex;
}
.tabs--container--with-border {
@include border-normal-bottom;
@apply border-b border-slate-50 dark:border-slate-800/50;
}
.tabs {
border-left-width: 0;
border-right-width: 0;
border-top-width: 0;
display: flex;
min-width: var(--space-mega);
padding: 0 var(--space-normal);
@apply border-r-0 border-l-0 border-t-0 flex min-w-[6.25rem] py-0 px-4;
}
.tabs--with-scroll {
@apply overflow-hidden py-0 px-1;
max-width: calc(100% - 64px);
overflow: hidden;
padding: 0 var(--space-smaller);
}
.tabs--scroll-button {
align-items: center;
border-radius: 0;
cursor: pointer;
display: flex;
height: auto;
justify-content: center;
min-width: var(--space-large);
@apply items-center rounded-none cursor-pointer flex h-auto justify-center min-w-[2rem];
}
// Tab chat type
.tab--chat-type {
@include flex;
@apply flex;
.tabs-title {
a {
font-size: var(--font-size-default);
font-weight: var(--font-weight-medium);
padding-bottom: var(--space-slab);
padding-top: var(--space-slab);
@apply text-base font-medium py-3;
}
}
}
.tabs-title {
flex-shrink: 0;
margin: 0 var(--space-small);
@apply flex-shrink-0 my-0 mx-2 ;
.badge {
background: var(--color-background);
border-radius: var(--space-small);
color: var(--color-gray);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-black);
margin: 0 var(--space-smaller);
padding: var(--space-smaller);
@apply bg-slate-50 dark:bg-slate-800 rounded-md text-slate-600 dark:text-slate-100 h-5 flex items-center justify-center text-xxs font-semibold my-0 mx-1 px-1 py-0;
}
&:first-child {
margin-left: 0;
@apply ml-0;
}
&:last-child {
margin-right: 0;
@apply mr-0;
}
&:hover,
&:focus {
a {
color: darken($medium-gray, 20%);
@apply text-slate-800 dark:text-slate-100;
}
}
a {
align-items: center;
border-bottom: 2px solid transparent;
color: $medium-gray;
display: flex;
flex-direction: row;
font-size: var(--font-size-small);
position: relative;
top: 1px;
@apply flex items-center flex-row border-b border-transparent text-slate-500 dark:text-slate-200 text-sm top-[1px] relative;
transition: border-color 0.15s $swift-ease-out-function;
}
&.is-active {
a {
border-bottom-color: var(--color-woot);
color: var(--color-woot);
@apply border-b border-woot-500 text-woot-500 dark:text-woot-500;
}
.badge {
background: $color-extra-light-blue;
color: var(--color-woot);
@apply bg-woot-50 dark:bg-woot-500 text-woot-500 dark:text-woot-50 dark:bg-opacity-40;
}
}
}

View File

@@ -1,22 +1,19 @@
table {
border-spacing: 0;
font-size: var(--font-size-small);
@apply border-spacing-0 text-sm;
thead {
th {
font-weight: var(--font-weight-bold);
text-align: left;
text-transform: uppercase;
@apply font-semibold tracking-[1px] text-left uppercase text-slate-900 dark:text-slate-200;
}
}
tbody {
tr {
border-bottom: 1px solid var(--color-border-light);
@apply border-b border-slate-50 dark:border-slate-800/30;
}
td {
padding: var(--space-small);
@apply p-2.5 text-slate-700 dark:text-slate-100;
}
}
}
@@ -24,37 +21,68 @@ table {
.woot-table {
tr {
.show-if-hover {
opacity: 0;
transition: opacity 0.2s $swift-ease-out-function;
@apply opacity-0;
}
&:hover {
.show-if-hover {
opacity: 1;
@apply opacity-100;
}
}
}
.agent-name {
display: block;
font-weight: var(--font-weight-medium);
text-transform: capitalize;
@apply block font-medium capitalize;
}
.woot-thumbnail {
border-radius: 50%;
height: 5rem;
width: 5rem;
@apply rounded-full h-[3.125rem] w-[3.125rem];
}
.button-wrapper {
@include flex-align(left, null);
@include flex;
flex-direction: row;
min-width: 20rem;
@apply flex justify-start flex-row min-w-[12.5rem] gap-1;
}
.button {
margin: 0;
}
}
.ve-table {
.ve-table-container.ve-table-border-around {
@apply border-slate-200 dark:border-slate-700;
}
.ve-table-content {
.ve-table-header .ve-table-header-tr .ve-table-header-th {
@apply bg-slate-50 dark:bg-slate-800 text-slate-800 dark:text-slate-100 border-slate-100 dark:border-slate-700/50;
}
.ve-table-body .ve-table-body-tr .ve-table-body-td {
@apply bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border-slate-75 dark:border-slate-800;
}
.ve-table-body.ve-table-row-hover .ve-table-body-tr:hover td {
@apply bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-100;
}
}
}
.table-pagination {
.ve-pagination-total {
@apply text-slate-600 dark:text-slate-200;
}
.ve-pagination-goto {
@apply text-slate-600 dark:text-slate-200;
}
.ve-pagination-li {
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200 border-slate-75 dark:border-slate-700;
}
.ve-pagination-goto-input {
@apply bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-200;
}
}

View File

@@ -1,15 +1,20 @@
<template>
<div class="cw-accordion">
<button class="cw-accordion--title drag-handle" @click="$emit('click')">
<div class="cw-accordion--title-wrap">
<emoji-or-icon class="icon-or-emoji" :icon="icon" :emoji="emoji" />
<h5>
<div class="-mt-px text-sm">
<button
class="flex items-center select-none w-full bg-slate-50 dark:bg-slate-800 border border-l-0 border-r-0 border-solid m-0 border-slate-100 dark:border-slate-700/50 cursor-grab justify-between py-2 px-4 drag-handle"
@click="$emit('click')"
>
<div class="flex justify-between mb-0.5">
<emoji-or-icon class="inline-block w-5" :icon="icon" :emoji="emoji" />
<h5
class="text-slate-800 text-sm dark:text-slate-100 mb-0 py-0 pr-2 pl-0"
>
{{ title }}
</h5>
</div>
<div class="button-icon--wrap">
<div class="flex flex-row">
<slot name="button" />
<div class="chevron-icon__wrap">
<div class="flex justify-end w-3 text-woot-500">
<fluent-icon v-if="isOpen" size="24" icon="subtract" type="solid" />
<fluent-icon v-else size="24" icon="add" type="solid" />
</div>
@@ -17,8 +22,8 @@
</button>
<div
v-if="isOpen"
class="cw-accordion--content"
:class="{ compact: compact }"
class="bg-white dark:bg-slate-900"
:class="compact ? 'p-0' : 'p-4'"
>
<slot />
</div>
@@ -56,67 +61,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.cw-accordion {
// This is done to fix contact sidebar border issues
// If you are using it else, find a fix to remove this hack
margin-top: -1px;
font-size: var(--font-size-small);
}
.cw-accordion--title {
align-items: center;
background: var(--s-50);
border-bottom: 1px solid var(--s-100);
border-top: 1px solid var(--s-100);
cursor: grab;
display: flex;
justify-content: space-between;
margin: 0;
padding: var(--space-small) var(--space-normal);
user-select: none;
width: 100%;
h5 {
font-size: var(--font-size-normal);
margin-bottom: 0;
padding: 0 var(--space-small) 0 0;
}
}
.cw-accordion--title-wrap {
display: flex;
justify-content: space-between;
margin-bottom: var(--space-micro);
}
.title-icon__wrap {
display: flex;
align-items: baseline;
}
.icon-or-emoji {
display: inline-block;
width: var(--space-two);
}
.button-icon--wrap {
display: flex;
flex-direction: row;
}
.chevron-icon__wrap {
display: flex;
justify-content: flex-end;
width: var(--space-slab);
color: var(--w-500);
}
.cw-accordion--content {
padding: var(--space-normal);
&.compact {
padding: 0;
}
}
</style>

View File

@@ -1,5 +1,8 @@
<template>
<button class="small-6 medium-4 large-3 channel" @click="$emit('click')">
<button
class="sm:w-[50%] md:w-[34%] lg:w-[25%] channel"
@click="$emit('click')"
>
<img :src="src" :alt="title" />
<h3 class="channel__title">
{{ title }}
@@ -24,44 +27,36 @@ export default {
<style scoped lang="scss">
.inactive {
filter: grayscale(100%);
img {
filter: grayscale(100%);
}
&.channel:hover {
@apply border-transparent shadow-none cursor-not-allowed;
}
}
.channel {
background: var(--white);
border: 1px solid var(--color-border-light);
cursor: pointer;
display: flex;
flex-direction: column;
margin: -1px;
padding: var(--space-normal) 0;
transition: all 0.2s ease-in;
align-items: center;
@apply bg-white dark:bg-slate-900 cursor-pointer flex flex-col transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800;
&:hover {
border: 1px solid var(--w-500);
box-shadow: var(--shadow-medium);
z-index: var(--z-index-high);
@apply border-woot-500 dark:border-woot-500 shadow-md z-50;
}
&.disabled {
opacity: 0.6;
@apply opacity-60;
}
img {
margin: var(--space-normal) auto;
width: 50%;
@apply w-[50%] my-4 mx-auto;
}
.channel__title {
color: var(--color-body);
font-size: var(--font-size-default);
text-align: center;
text-transform: capitalize;
@apply text-slate-800 dark:text-slate-100 text-base text-center capitalize;
}
p {
color: var(--b-500);
width: 100%;
@apply text-slate-600 dark:text-slate-300 w-full text-sm;
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="conversations-list-wrap"
class="conversations-list-wrap flex-basis-clamp flex-shrink-0 flex-basis-custom overflow-hidden flex flex-col border-r rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
:class="{
hide: !showConversationList,
'list--full-width': isOnExpandedLayout,
@@ -8,26 +8,28 @@
>
<slot />
<div
class="chat-list__top"
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
class="flex items-center justify-between py-0 px-4"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700': hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex-center chat-list__title">
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="page-sub-title text-truncate margin-bottom-0"
class="text-xl break-words overflow-hidden whitespace-nowrap text-ellipsis text-black-900 dark:text-slate-100 mb-0"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="conversation--status-pill"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{
this.$t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`)
}}
</span>
</div>
<div class="filter--actions">
<div class="flex items-center gap-1">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
@@ -104,7 +106,10 @@
@chatTabChange="updateAssigneeTab"
/>
<p v-if="!chatListLoading && !conversationList.length" class="content-box">
<p
v-if="!chatListLoading && !conversationList.length"
class="overflow-auto p-4 flex justify-center items-center"
>
{{ $t('CHAT_LIST.LIST.404') }}
</p>
<conversation-bulk-actions
@@ -123,47 +128,46 @@
/>
<div
ref="activeConversation"
class="conversations-list"
:class="{ 'is-context-menu-open': isContextMenuOpen }"
class="conversations-list flex-1"
:class="{ 'overflow-hidden': isContextMenuOpen }"
>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
<div>
<conversation-card
v-for="chat in conversationList"
:key="chat.id"
:active-label="label"
:team-id="teamId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
:selected="isConversationSelected(chat.id)"
@select-conversation="selectConversation"
@de-select-conversation="deSelectConversation"
@assign-agent="onAssignAgent"
@assign-team="onAssignTeam"
@assign-label="onAssignLabels"
@update-conversation-status="toggleConversationStatus"
@context-menu-toggle="onContextMenuToggle"
@mark-as-unread="markAsUnread"
@assign-priority="assignPriority"
/>
</div>
<div v-if="chatListLoading" class="text-center">
<span class="spinner" />
<span class="spinner mt-4 mb-4" />
</div>
<woot-button
v-if="!hasCurrentPageEndReached && !chatListLoading"
variant="clear"
size="expanded"
class="load-more--button"
@click="loadMoreConversations"
>
{{ $t('CHAT_LIST.LOAD_MORE_CONVERSATIONS') }}
</woot-button>
<p
v-if="showEndOfListMessage"
class="text-center text-muted end-of-list-text"
>
<p v-if="showEndOfListMessage" class="text-center text-muted p-4">
{{ $t('CHAT_LIST.EOF') }}
</p>
</div>
@@ -954,72 +958,41 @@ export default {
},
};
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/woot';
.spinner {
margin-top: var(--space-normal);
margin-bottom: var(--space-normal);
}
.conversations-list {
// Prevent the list from scrolling if the submenu is opened
&.is-context-menu-open {
overflow: hidden !important;
<style scoped>
@tailwind components;
@layer components {
.flex-basis-clamp {
flex-basis: clamp(20rem, 4vw + 21.25rem, 27.5rem);
}
}
</style>
<style scoped lang="scss">
.conversations-list-wrap {
flex-shrink: 0;
flex-basis: clamp(32rem, 4vw + 34rem, 44rem);
overflow: hidden;
&.hide {
display: none;
@apply hidden;
}
&.list--full-width {
flex-basis: 100%;
}
.page-sub-title {
font-size: var(--font-size-two);
@apply basis-full;
}
}
.filter--actions {
display: flex;
align-items: center;
gap: var(--space-smaller);
.conversations-list {
@apply overflow-hidden hover:overflow-y-auto;
}
.filter__applied {
padding-bottom: var(--space-slab) !important;
border-bottom: 1px solid var(--color-border);
.load-more--button {
@apply text-center rounded-none;
}
.tab--chat-type {
padding: 0 var(--space-normal);
@apply py-0 px-4;
::v-deep {
.tabs {
padding: 0;
@apply p-0;
}
}
}
.conversation--status-pill {
background: var(--color-background);
border-radius: var(--border-radius-small);
color: var(--color-medium-gray);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);
margin: var(--space-micro) var(--space-small) 0;
padding: var(--space-smaller);
text-transform: capitalize;
}
.chat-list__title {
max-width: 85%;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="custom-attribute">
<div class="title-wrap">
<h4 class="text-block-title title error">
<div class="py-3 px-4">
<div class="flex items-center mb-1">
<h4 class="text-block-title flex items-center m-0 w-full error">
<div v-if="isAttributeTypeCheckbox" class="checkbox-wrap">
<input
v-model="editedValue"
@@ -10,7 +10,7 @@
@change="onUpdate"
/>
</div>
<div class="name-button__wrap">
<div class="flex items-center justify-between w-full">
<span
class="attribute-name"
:class="{ error: $v.editedValue.$error }"
@@ -24,7 +24,7 @@
size="medium"
color-scheme="secondary"
icon="delete"
class-names="delete-button"
class-names="flex justify-end w-4"
@click="onDelete"
/>
</div>
@@ -47,7 +47,10 @@
<woot-button size="small" icon="checkmark" @click="onUpdate" />
</div>
</div>
<span v-if="shouldShowErrorMessage" class="error-message">
<span
v-if="shouldShowErrorMessage"
class="text-red-400 dark:text-red-500 text-sm block font-normal -mt-px w-full"
>
{{ errorMessage }}
</span>
</div>
@@ -61,14 +64,17 @@
:href="value"
target="_blank"
rel="noopener noreferrer"
class="value"
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
>
{{ urlValue }}
</a>
<p v-else class="value">
<p
v-else
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
>
{{ displayValue || '---' }}
</p>
<div class="action-buttons__wrap">
<div class="flex max-w-[2rem] gap-1 ml-1 rtl:mr-1 rtl:ml-0">
<woot-button
v-if="showActions"
v-tooltip="$t('CUSTOM_ATTRIBUTES.ACTIONS.COPY')"
@@ -269,98 +275,45 @@ export default {
</script>
<style lang="scss" scoped>
.custom-attribute {
padding: var(--space-slab) var(--space-normal);
}
.title-wrap {
display: flex;
align-items: center;
margin-bottom: var(--space-mini);
}
.title {
display: flex;
align-items: center;
margin: 0;
width: 100%;
}
.checkbox-wrap {
display: flex;
align-items: center;
@apply flex items-center;
}
.checkbox {
margin: 0 var(--space-small) 0 0;
}
.name-button__wrap {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
@apply my-0 mr-2 ml-0;
}
.attribute-name {
width: 100%;
@apply w-full text-slate-800 dark:text-slate-100;
&.error {
color: var(--r-400);
@apply text-red-400 dark:text-red-500;
}
}
.title--icon {
width: var(--space-two);
}
.edit-button {
display: none;
}
.delete-button {
display: flex;
justify-content: flex-end;
width: var(--space-normal);
@apply hidden;
}
.value--view {
display: flex;
@apply flex;
&.is-editable:hover {
.value {
background: var(--color-background);
margin-bottom: 0;
@apply bg-slate-50 dark:bg-slate-700 mb-0;
}
.edit-button {
display: block;
@apply block;
}
}
.action-buttons__wrap {
display: flex;
max-width: var(--space-larger);
}
}
.value {
display: inline-block;
min-width: var(--space-mega);
border-radius: var(--border-radius-small);
margin-bottom: 0;
word-break: break-all;
padding: var(--space-micro) var(--space-smaller);
}
.error-message {
color: var(--r-400);
display: block;
font-size: 1.4rem;
font-size: var(--font-size-small);
font-weight: 400;
margin-bottom: 1rem;
margin-top: -1.6rem;
width: 100%;
}
::v-deep {
.selector-wrap {
margin: 0;
top: var(--space-smaller);
@apply m-0 top-1;
.selector-name {
margin-left: 0;
@apply ml-0;
}
}
.name {
margin-left: 0;
@apply ml-0;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<div class="flex flex-col">
<woot-modal-header :header-title="$t('CONVERSATION.CUSTOM_SNOOZE.TITLE')" />
<form class="modal-content" @submit.prevent="chooseTime">
<date-picker
v-model="snoozeTime"
type="datetime"
inline
:lang="lang"
:disabled-date="disabledDate"
:disabled-time="disabledTime"
:popup-style="{ width: '100%' }"
/>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-button variant="clear" @click.prevent="onClose">
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
</woot-button>
<woot-button>
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.APPLY') }}
</woot-button>
</div>
</form>
</div>
</template>
<script>
import DatePicker from 'vue2-datepicker';
export default {
components: {
DatePicker,
},
data() {
return {
snoozeTime: null,
lang: {
days: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
yearFormat: 'YYYY',
monthFormat: 'MMMM',
},
};
},
methods: {
onClose() {
this.$emit('close');
},
chooseTime() {
this.$emit('choose-time', this.snoozeTime);
},
disabledDate(date) {
// Disable all the previous dates
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return date < yesterday;
},
disabledTime(date) {
// Allow only time after 1 hour
const now = new Date();
now.setHours(now.getHours() + 1);
return date < now;
},
},
};
</script>
<style lang="scss" scoped>
.modal-content {
@apply pt-2 px-5 pb-6;
}
</style>

View File

@@ -8,6 +8,7 @@
>
<div :class="modalContainerClassName" @click.stop>
<woot-button
v-if="showCloseButton"
color-scheme="secondary"
icon="dismiss"
variant="clear"
@@ -28,6 +29,10 @@ export default {
default: true,
},
show: Boolean,
showCloseButton: {
type: Boolean,
default: true,
},
onClose: {
type: Function,
required: true,
@@ -47,7 +52,8 @@ export default {
},
computed: {
modalContainerClassName() {
let className = 'modal-container skip-context-menu';
let className =
'modal-container bg-white dark:bg-slate-800 skip-context-menu';
if (this.fullWidth) {
return `${className} modal-container--full-width`;
}
@@ -101,7 +107,7 @@ export default {
.modal-container {
border-radius: 0;
height: 100%;
width: 48rem;
width: 30rem;
}
}
.modal-big {

View File

@@ -1,12 +1,22 @@
<template>
<div class="column page-top-bar">
<div class="flex flex-col items-start pt-8 px-8 pb-0">
<img v-if="headerImage" :src="headerImage" alt="No image" />
<h2 class="page-sub-title">
<h2
ref="modalHeaderTitle"
class="text-slate-800 text-lg dark:text-slate-100"
>
{{ headerTitle }}
</h2>
<p v-if="headerContent" class="small-12 column wrap-content">
<p
v-if="headerContent"
ref="modalHeaderContent"
class="w-full break-words text-slate-600 mt-2 text-sm dark:text-slate-300"
>
{{ headerContent }}
<span v-if="headerContentValue" class="content-value">
<span
v-if="headerContentValue"
class="font-semibold text-sm text-slate-600 dark:text-slate-300"
>
{{ headerContentValue }}
</span>
</p>
@@ -36,13 +46,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.wrap-content {
word-wrap: break-word;
margin-top: var(--space-small);
.content-value {
font-weight: var(--font-weight-bold);
}
}
</style>

View File

@@ -77,7 +77,7 @@ export default {
@import '~dashboard/assets/scss/mixins';
.ui-notification-container {
max-width: 40rem;
max-width: 25rem;
position: absolute;
right: var(--space-normal);
top: var(--space-normal);
@@ -94,7 +94,7 @@ export default {
border-radius: var(--border-radius-medium);
box-shadow: var(--shadow-large);
min-width: 24rem;
min-width: 15rem;
padding: var(--space-normal);
}

View File

@@ -1,20 +1,28 @@
<template>
<div class="row settings--section">
<div class="medium-4 small-12 title--section">
<p class="sub-block-title">
<div
class="ml-0 mr-0 flex pt-0 pr-4 pb-4 pl-0"
:class="{
'pt-4 border-b border-solid border-slate-50 dark:border-slate-700/30': showBorder,
}"
>
<div class="w-[30%] min-w-0 max-w-[30%] pr-12">
<p
v-if="title"
class="text-base text-woot-500 dark:text-woot-500 mb-0 font-medium"
>
{{ title }}
</p>
<p class="sub-head">
<slot name="subTitle">
<p class="text-sm mb-2">
<slot v-if="subTitle" name="subTitle">
{{ subTitle }}
</slot>
</p>
<p v-if="note">
<span class="note">{{ $t('INBOX_MGMT.NOTE') }}</span>
<span class="font-semibold">{{ $t('INBOX_MGMT.NOTE') }}</span>
{{ note }}
</p>
</div>
<div class="medium-6 small-12">
<div class="w-[50%] min-w-0 max-w-[50%]">
<slot />
</div>
</div>
@@ -25,11 +33,15 @@ export default {
props: {
title: {
type: String,
required: true,
default: '',
},
subTitle: {
type: String,
required: true,
default: '',
},
showBorder: {
type: Boolean,
default: true,
},
note: {
type: String,
@@ -38,27 +50,3 @@ export default {
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.settings--section {
border-bottom: 1px solid $color-border;
display: flex;
padding: $space-normal $space-normal $space-normal 0;
.sub-block-title {
color: $color-woot;
font-weight: $font-weight-medium;
margin-bottom: 0;
}
.title--section {
padding-right: var(--space-large);
}
.note {
font-weight: var(--font-weight-bold);
}
}
</style>

View File

@@ -1,10 +1,10 @@
<template>
<woot-button
size="small"
:size="size"
variant="clear"
color-scheme="secondary"
class="-ml-3 text-black-900 dark:text-slate-300"
icon="list"
class="toggle-sidebar"
@click="onMenuItemClick"
/>
</template>
@@ -13,6 +13,12 @@
import { BUS_EVENTS } from 'shared/constants/busEvents';
export default {
props: {
size: {
type: String,
default: 'small',
},
},
methods: {
onMenuItemClick() {
bus.$emit(BUS_EVENTS.TOGGLE_SIDEMENU);
@@ -20,8 +26,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.toggle-sidebar {
margin-left: var(--space-minus-small);
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<banner
v-if="shouldShowBanner"
color-scheme="alert"
:banner-message="bannerMessage"
:action-button-label="actionButtonMessage"
has-action-button
@click="routeToBilling"
/>
</template>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import { mapGetters } from 'vuex';
import adminMixin from 'dashboard/mixins/isAdmin';
import accountMixin from 'dashboard/mixins/account';
const EMPTY_SUBSCRIPTION_INFO = {
status: null,
endsOn: null,
};
export default {
components: { Banner },
mixins: [adminMixin, accountMixin],
computed: {
...mapGetters({
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
getAccount: 'accounts/getAccount',
}),
bannerMessage() {
return this.$t('GENERAL_SETTINGS.PAYMENT_PENDING');
},
actionButtonMessage() {
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
},
shouldShowBanner() {
if (!this.isOnChatwootCloud) {
return false;
}
if (!this.isAdmin) {
return false;
}
return this.isPaymentPending();
},
},
methods: {
routeToBilling() {
this.$router.push({
name: 'billing_settings_index',
params: { accountId: this.accountId },
});
},
isPaymentPending() {
const { status, endsOn } = this.getSubscriptionInfo();
if (status && endsOn) {
const now = new Date();
if (status === 'past_due' && endsOn < now) {
return true;
}
}
return false;
},
getSubscriptionInfo() {
const account = this.getAccount(this.accountId);
if (!account) return EMPTY_SUBSCRIPTION_INFO;
const { custom_attributes: subscription } = account;
if (!subscription) return EMPTY_SUBSCRIPTION_INFO;
const {
subscription_status: status,
subscription_ends_on: endsOn,
} = subscription;
return { status, endsOn: new Date(endsOn) };
},
},
};
</script>

View File

@@ -0,0 +1,89 @@
<template>
<banner
v-if="shouldShowBanner"
color-scheme="alert"
:banner-message="bannerMessage"
:action-button-label="actionButtonMessage"
has-action-button
@click="routeToBilling"
/>
</template>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import { mapGetters } from 'vuex';
import adminMixin from 'dashboard/mixins/isAdmin';
import accountMixin from 'dashboard/mixins/account';
import { differenceInDays } from 'date-fns';
export default {
components: { Banner },
mixins: [adminMixin, accountMixin],
data() {
return { conversationMeta: {} };
},
computed: {
...mapGetters({
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
getAccount: 'accounts/getAccount',
}),
bannerMessage() {
return this.$t('GENERAL_SETTINGS.LIMITS_UPGRADE');
},
actionButtonMessage() {
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
},
shouldShowBanner() {
if (!this.isOnChatwootCloud) {
return false;
}
if (this.isTrialAccount()) {
return false;
}
return this.isLimitExceeded();
},
},
mounted() {
if (this.isOnChatwootCloud) {
this.fetchLimits();
}
},
methods: {
fetchLimits() {
this.$store.dispatch('accounts/limits');
},
routeToBilling() {
this.$router.push({
name: 'billing_settings_index',
params: { accountId: this.accountId },
});
},
isTrialAccount() {
// check if account is less than 15 days old
const account = this.getAccount(this.accountId);
if (!account) return false;
const createdAt = new Date(account.created_at);
const diffDays = differenceInDays(new Date(), createdAt);
return diffDays <= 15;
},
isLimitExceeded() {
const account = this.getAccount(this.accountId);
if (!account) return false;
const { limits } = account;
if (!limits) return false;
const { conversation, non_web_inboxes: nonWebInboxes } = limits;
return this.testLimit(conversation) || this.testLimit(nonWebInboxes);
},
testLimit({ allowed, consumed }) {
return consumed > allowed;
},
},
};
</script>

View File

@@ -0,0 +1,21 @@
<template>
<kbd class="hotkey p-0.5 min-w-[1rem] uppercase" :class="customClass">
<slot />
</kbd>
</template>
<script>
export default {
props: {
customClass: {
type: String,
default: '',
},
},
};
</script>
<style lang="scss">
kbd.hotkey {
@apply inline-flex leading-[0.625rem] rounded tracking-wide flex-shrink-0 items-center select-none justify-center;
}
</style>

View File

@@ -8,7 +8,7 @@
>
<fluent-icon v-if="!!iconClass" :icon="iconClass" class="icon" />
<span>{{ buttonText }}</span>
<spinner v-if="loading" />
<spinner v-if="loading" class="ml-2" :color-scheme="spinnerClass" />
</button>
</template>
@@ -40,6 +40,10 @@ export default {
type: String,
default: '',
},
spinnerClass: {
type: String,
default: '',
},
type: {
type: String,
default: 'submit',
@@ -47,7 +51,7 @@ export default {
},
computed: {
computedClass() {
return `button nice ${this.buttonClass || ' '}`;
return `button nice gap-2 ${this.buttonClass || ' '}`;
},
},
methods: {
@@ -59,11 +63,9 @@ export default {
</script>
<style lang="scss" scoped>
button:disabled {
opacity: 1;
background-color: var(--w-100);
@apply bg-woot-100 dark:bg-woot-500/25 dark:text-slate-500 opacity-100;
&:hover {
background-color: var(--w-100);
@apply bg-woot-100 dark:bg-woot-500/25;
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="resolve-actions">
<div class="resolve-actions relative flex items-center justify-end">
<div class="button-group">
<woot-button
v-if="isOpen"
@@ -49,6 +49,17 @@
class="dropdown-pane dropdown-pane--open"
>
<woot-dropdown-menu>
<woot-dropdown-item v-if="!isPending">
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="snooze"
@click="() => openSnoozeModal()"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item v-if="!isPending">
<woot-button
variant="clear"
@@ -60,71 +71,35 @@
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider v-if="isOpen" />
<woot-dropdown-sub-menu
v-if="isOpen"
:title="this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.TITLE')"
>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="send-clock"
@click="() => toggleStatus(STATUS_TYPE.SNOOZED, null)"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.NEXT_REPLY') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="dual-screen-clock"
@click="
() => toggleStatus(STATUS_TYPE.SNOOZED, snoozeTimes.tomorrow)
"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.TOMORROW') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="calendar-clock"
@click="
() => toggleStatus(STATUS_TYPE.SNOOZED, snoozeTimes.nextWeek)
"
>
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE.NEXT_WEEK') }}
</woot-button>
</woot-dropdown-item>
</woot-dropdown-sub-menu>
</woot-dropdown-menu>
</div>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div>
</template>
<script>
import { getUnixTime } from 'date-fns';
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin';
import snoozeTimesMixin from 'dashboard/mixins/conversation/snoozeTimesMixin.js';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import {
hasPressedAltAndEKey,
hasPressedCommandPlusAltAndEKey,
hasPressedAltAndMKey,
} from 'shared/helpers/KeyboardHelpers';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownSubMenu from 'shared/components/ui/dropdown/DropdownSubMenu.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider';
import wootConstants from 'dashboard/constants/globals';
import {
@@ -137,16 +112,16 @@ export default {
components: {
WootDropdownItem,
WootDropdownMenu,
WootDropdownSubMenu,
WootDropdownDivider,
CustomSnoozeModal,
},
mixins: [clickaway, alertMixin, eventListenerMixins, snoozeTimesMixin],
mixins: [clickaway, alertMixin, eventListenerMixins],
props: { conversationId: { type: [String, Number], required: true } },
data() {
return {
isLoading: false,
showActionsDropdown: false,
STATUS_TYPE: wootConstants.STATUS_TYPE,
showCustomSnoozeModal: false,
};
},
computed: {
@@ -218,10 +193,26 @@ export default {
}
},
onCmdSnoozeConversation(snoozeType) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
this.snoozeTimes[snoozeType] || null
);
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
hideCustomSnoozeModal() {
this.showCustomSnoozeModal = false;
},
onCmdOpenConversation() {
this.toggleStatus(this.STATUS_TYPE.OPEN);
@@ -252,23 +243,19 @@ export default {
this.isLoading = false;
});
},
openSnoozeModal() {
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'snooze_conversation' });
},
},
};
</script>
<style lang="scss" scoped>
.resolve-actions {
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
}
.dropdown-pane {
left: unset;
top: 4.2rem;
margin-top: var(--space-micro);
right: 0;
max-width: 20rem;
min-width: 15.6rem;
@apply left-auto top-[2.625rem] mt-0.5 right-0 max-w-[12.5rem] min-w-[9.75rem];
.dropdown-menu__item {
@apply mb-0;
}
}
</style>

View File

@@ -4,7 +4,7 @@
<woot-dropdown-item
v-for="status in availabilityStatuses"
:key="status.value"
class="status-items"
class="flex items-baseline"
>
<woot-button
size="small"
@@ -18,23 +18,25 @@
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider />
<woot-dropdown-item class="auto-offline--toggle">
<div class="info-wrap">
<woot-dropdown-item class="m-0 flex items-center justify-between p-2">
<div class="flex items-center">
<fluent-icon
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
icon="info"
size="14"
class="info-icon"
class="mt-px"
/>
<span class="auto-offline--text">
<span
class="my-0 mx-1 text-xs font-medium text-slate-600 dark:text-slate-100"
>
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
</span>
</div>
<woot-switch
size="small"
class="auto-offline--switch"
class="mt-px mx-1 mb-0"
:value="currentUserAutoOffline"
@input="updateAutoOffline"
/>
@@ -138,71 +140,3 @@ export default {
},
};
</script>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.status {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: var(--space-micro) var(--space-smaller);
}
.status-view {
display: flex;
align-items: baseline;
& &--title {
color: var(--b-600);
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
margin-left: var(--space-small);
&:first-letter {
text-transform: capitalize;
}
}
}
.status-change {
.dropdown-pane {
top: -132px;
right: var(--space-normal);
}
.status-items {
display: flex;
align-items: baseline;
}
}
.auto-offline--toggle {
align-items: center;
display: flex;
justify-content: space-between;
padding: var(--space-smaller);
margin: 0;
.info-wrap {
display: flex;
align-items: center;
}
.info-icon {
margin-top: -1px;
}
.auto-offline--switch {
margin: -1px var(--space-micro) 0;
}
.auto-offline--text {
margin: 0 var(--space-smaller);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
color: var(--s-700);
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<aside class="woot-sidebar">
<aside class="h-full flex">
<primary-sidebar
:logo-source="globalConfig.logoThumbnail"
:installation-name="globalConfig.installationName"
@@ -11,22 +11,21 @@
@key-shortcut-modal="toggleKeyShortcutModal"
@open-notification-panel="openNotificationPanel"
/>
<div class="secondary-sidebar">
<secondary-sidebar
v-if="showSecondarySidebar"
:class="sidebarClassName"
:account-id="accountId"
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</div>
<secondary-sidebar
v-if="showSecondarySidebar"
:class="sidebarClassName"
:account-id="accountId"
:inboxes="inboxes"
:labels="labels"
:teams="teams"
:custom-views="customViews"
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
:is-on-chatwoot-cloud="isOnChatwootCloud"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</aside>
</template>
@@ -214,87 +213,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.woot-sidebar {
background: var(--white);
display: flex;
min-height: 0;
height: 100%;
width: fit-content;
}
</style>
<style lang="scss">
@import '~dashboard/assets/scss/variables';
.account-selector--modal {
.modal-container {
width: 40rem;
}
}
.secondary-sidebar {
overflow-y: auto;
height: 100%;
}
.account-selector {
cursor: pointer;
padding: $space-small $space-large;
.selected--account {
margin-top: -$space-smaller;
& + .account--details {
padding-left: $space-normal - $space-micro;
}
}
.account--details {
padding-left: $space-large + $space-smaller;
}
&:last-child {
margin-bottom: $space-large;
}
a {
align-items: center;
cursor: pointer;
display: flex;
.account--name {
cursor: pointer;
font-size: $font-size-medium;
font-weight: $font-weight-medium;
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: $font-size-mini;
text-transform: capitalize;
}
}
}
.app-context-menu {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: row;
height: 6rem;
}
.current-user--options {
font-size: $font-size-big;
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
}
.secondary-menu .nested.vertical.menu {
margin-left: var(--space-small);
}
</style>

View File

@@ -29,6 +29,7 @@ const settings = accountId => ({
'settings_inboxes_page_channel',
'settings_integrations_dashboard_apps',
'settings_integrations_integration',
'settings_integrations_slack',
'settings_integrations_webhook',
'settings_integrations',
'settings_teams_add_agents',
@@ -92,7 +93,6 @@ const settings = accountId => ({
{
icon: 'automation',
label: 'AUTOMATION',
beta: true,
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
toStateName: 'automation_list',
@@ -101,7 +101,6 @@ const settings = accountId => ({
{
icon: 'bot',
label: 'AGENT_BOTS',
beta: true,
hasSubMenu: false,
globalConfigFlag: 'csmlEditorHost',
toState: frontendURL(`accounts/${accountId}/settings/agent-bots`),
@@ -114,7 +113,6 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/macros`),
toStateName: 'macros_wrapper',
beta: true,
featureFlag: FEATURE_FLAGS.MACROS,
},
{

View File

@@ -1,25 +1,31 @@
<template>
<div
v-if="showShowCurrentAccountContext"
class="account-context--group"
class="text-slate-700 dark:text-slate-200 rounded-md text-xs py-2 px-2 mt-2 relative border border-slate-50 dark:border-slate-800/50 hover:bg-slate-50 dark:hover:bg-slate-800 cursor-pointer"
@mouseover="setShowSwitch"
@mouseleave="resetShowSwitch"
>
{{ $t('SIDEBAR.CURRENTLY_VIEWING_ACCOUNT') }}
<p class="account-context--name text-ellipsis">
<p
class="text-ellipsis overflow-hidden whitespace-nowrap font-medium mb-0 text-slate-800 dark:text-slate-100"
>
{{ account.name }}
</p>
<transition name="fade">
<div v-if="showSwitchButton" class="account-context--switch-group">
<woot-button
variant="clear"
size="tiny"
icon="arrow-swap"
class="switch-button"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
<div
v-if="showSwitchButton"
class="ltr:overlay-shadow ltr:dark:overlay-shadow-dark rtl:rtl-overlay-shadow rtl:dark:rtl-overlay-shadow-dark flex items-center h-full rounded-md justify-end absolute top-0 right-0 w-full"
>
<div class="my-0 mx-2">
<woot-button
variant="clear"
size="tiny"
icon="arrow-swap"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
</div>
</div>
</transition>
</div>
@@ -50,52 +56,41 @@ export default {
},
};
</script>
<style scoped lang="scss">
.account-context--group {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
font-size: var(--font-size-mini);
padding: var(--space-small);
margin: var(--space-small) var(--space-small) 0 var(--space-small);
position: relative;
&:hover {
background: var(--b-100);
<style scoped>
@tailwind components;
@layer components {
.overlay-shadow {
background-image: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
}
.account-context--name {
font-weight: var(--font-weight-medium);
margin-bottom: 0;
.overlay-shadow-dark {
background-image: linear-gradient(
to right,
rgba(0, 0, 0, 0) 0%,
rgb(21, 23, 24) 50%
);
}
.rtl-overlay-shadow {
background-image: linear-gradient(
to left,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
}
.rtl-overlay-shadow-dark {
background-image: linear-gradient(
to left,
rgba(0, 0, 0, 0) 0%,
rgb(21, 23, 24) 50%
);
}
}
.switch-button {
margin: 0 var(--space-small);
}
.account-context--switch-group {
--overlay-shadow: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
align-items: center;
background-image: var(--overlay-shadow);
border-top-left-radius: 0;
border-top-right-radius: var(--border-radius-normal);
border-bottom-left-radius: 0;
border-bottom-right-radius: var(--border-radius-normal);
display: flex;
height: 100%;
justify-content: flex-end;
opacity: 1;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
@@ -103,6 +98,6 @@ export default {
.fade-enter,
.fade-leave-to {
opacity: 0;
@apply opacity-0;
}
</style>

View File

@@ -2,31 +2,39 @@
<woot-modal
:show="showAccountModal"
:on-close="() => $emit('close-account-modal')"
class="account-selector--modal"
>
<woot-modal-header
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div class="account-selector--wrap">
<div class="px-8 pt-4 pb-8">
<div
v-for="account in currentUser.accounts"
:id="`account-${account.id}`"
:key="account.id"
class="account-selector"
class="pt-0 pb-0"
>
<button
class="button expanded clear link"
class="flex justify-between items-center expanded clear link cursor-pointer px-4 py-3 w-full rounded-lg hover:underline hover:bg-slate-25 dark:hover:bg-slate-900"
@click="onChangeAccount(account.id)"
>
<span class="button__content">
<label :for="account.name" class="account-details--wrap">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
<span class="w-full">
<label :for="account.name" class="text-left rtl:text-right">
<div
class="text-slate-700 text-lg dark:text-slate-100 font-medium hover:underline-offset-4 leading-5"
>
{{ account.name }}
</div>
<div
class="text-slate-500 text-xs dark:text-slate-500 font-medium hover:underline-offset-4"
>
{{ account.role }}
</div>
</label>
</span>
<fluent-icon
v-show="account.id === accountId"
class="selected--account"
class="text-slate-800 dark:text-slate-100"
icon="checkmark-circle"
type="solid"
size="24"
@@ -74,32 +82,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.account-selector--wrap {
margin-top: var(--space-normal);
}
.account-selector {
padding-top: 0;
padding-bottom: 0;
.button {
display: flex;
justify-content: space-between;
padding: var(--space-one) var(--space-normal);
.account-details--wrap {
text-align: left;
.account--name {
cursor: pointer;
font-size: var(--font-size-medium);
font-weight: var(--font-weight-medium);
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: var(--font-size-mini);
text-transform: capitalize;
}
}
}
}
</style>

View File

@@ -2,24 +2,23 @@
<woot-modal
:show="show"
:on-close="() => $emit('close-account-create-modal')"
class="account-selector--modal"
>
<div class="column content-box">
<div class="h-auto overflow-auto flex flex-col">
<woot-modal-header
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
/>
<div v-if="!hasAccounts" class="alert-wrap">
<div class="callout alert">
<div class="icon-wrap">
<div v-if="!hasAccounts" class="text-sm mt-6 mx-8 mb-0">
<div class="items-center rounded-md flex alert">
<div class="ml-1 mr-3">
<fluent-icon icon="warning" />
</div>
{{ $t('CREATE_ACCOUNT.NO_ACCOUNT_WARNING') }}
</div>
</div>
<form class="row" @submit.prevent="addAccount">
<div class="medium-12 columns">
<form class="flex flex-col w-full" @submit.prevent="addAccount">
<div class="w-full">
<label :class="{ error: $v.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
@@ -30,8 +29,8 @@
/>
</label>
</div>
<div class="modal-footer medium-12 columns">
<div class="medium-12 columns">
<div class="w-full">
<div class="w-full">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
@@ -102,20 +101,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.alert-wrap {
font-size: var(--font-size-small);
margin: var(--space-medium) var(--space-large) var(--space-zero);
.callout {
align-items: center;
border-radius: var(--border-radius-normal);
display: flex;
}
}
.icon-wrap {
margin-left: var(--space-smaller);
margin-right: var(--space-slab);
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="logo">
<div class="w-8 h-8">
<router-link :to="dashboardPath" replace>
<img :src="source" :alt="name" />
</router-link>
@@ -30,17 +30,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
$logo-size: 32px;
.logo {
padding: var(--space-normal);
img {
width: $logo-size;
height: $logo-size;
object-fit: cover;
object-position: left center;
}
}
</style>

View File

@@ -1,15 +1,25 @@
<template>
<div class="notifications-link">
<woot-button
class-names="notifications-link--button"
variant="clear"
color-scheme="secondary"
:class="{ 'is-active': isNotificationPanelActive }"
<div class="mb-4">
<button
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
:class="{
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50': isNotificationPanelActive,
}"
@click="openNotificationPanel"
>
<fluent-icon icon="alert" />
<span v-if="unreadCount" class="badge warning">{{ unreadCount }}</span>
</woot-button>
<fluent-icon
icon="alert"
:class="{
'text-woot-500': isNotificationPanelActive,
}"
/>
<span
v-if="unreadCount"
class="text-black-900 bg-yellow-300 absolute -top-0.5 -right-1 p-1 text-xxs min-w-[1rem] rounded-full"
>
{{ unreadCount }}
</span>
</button>
</div>
</template>
<script>
@@ -43,37 +53,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.notifications-link {
margin-bottom: var(--space-small);
}
.badge {
position: absolute;
right: var(--space-minus-smaller);
top: var(--space-minus-smaller);
}
.notifications-link--button {
display: flex;
position: relative;
border-radius: var(--border-radius-large);
border: 1px solid transparent;
color: var(--s-600);
margin: var(--space-small) 0;
&:hover {
background: var(--w-50);
color: var(--s-600);
}
&:focus {
border-color: var(--w-500);
}
&.is-active {
background: var(--w-50);
color: var(--w-500);
}
}
</style>

View File

@@ -3,11 +3,10 @@
<div
v-if="show"
v-on-clickaway="onClickAway"
class="options-menu dropdown-pane"
:class="{ 'dropdown-pane--open': show }"
class="left-3 rtl:left-auto rtl:right-3 bottom-16 w-64 absolute z-20 rounded-md shadow-xl bg-white dark:bg-slate-800 py-2 px-2 border border-slate-25 dark:border-slate-700"
:class="{ 'block visible': show }"
>
<availability-status />
<li class="divider" />
<woot-dropdown-menu>
<woot-dropdown-item v-if="showChangeAccountOption">
<woot-button
@@ -50,7 +49,7 @@
>
<a
:href="href"
class="button small clear secondary"
class="button small clear secondary bg-white dark:bg-slate-800 h-8"
:class="{ 'is-active': isActive }"
@click="e => handleProfileSettingClick(e, navigate)"
>
@@ -61,10 +60,21 @@
</a>
</router-link>
</woot-dropdown-item>
<woot-dropdown-item>
<woot-button
variant="clear"
color-scheme="secondary"
size="small"
icon="appearance"
@click="openAppearanceOptions"
>
{{ $t('SIDEBAR_ITEMS.APPEARANCE') }}
</woot-button>
</woot-dropdown-item>
<woot-dropdown-item v-if="currentUser.type === 'SuperAdmin'">
<a
href="/super_admin"
class="button small clear secondary"
class="button small clear secondary bg-white dark:bg-slate-800 h-8"
target="_blank"
rel="noopener nofollow noreferrer"
@click="$emit('close')"
@@ -146,15 +156,10 @@ export default {
onClickAway() {
if (this.show) this.$emit('close');
},
openAppearanceOptions() {
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'appearance_settings' });
},
},
};
</script>
<style lang="scss" scoped>
.options-menu.dropdown-pane {
left: var(--space-slab);
bottom: var(--space-larger);
min-width: var(--space-giga);
top: unset;
z-index: var(--z-index-low);
}
</style>

View File

@@ -1,11 +1,14 @@
<template>
<div class="primary--sidebar">
<logo
:source="logoSource"
:name="installationName"
:account-id="accountId"
/>
<nav class="menu vertical">
<div
class="h-full w-16 bg-white dark:bg-slate-900 border-r border-slate-50 dark:border-slate-800/50 rtl:border-l rtl:border-r-0 flex justify-between flex-col"
>
<div class="flex flex-col items-center">
<logo
:source="logoSource"
:name="installationName"
:account-id="accountId"
class="m-4 mb-10"
/>
<primary-nav-item
v-for="menuItem in menuItems"
:key="menuItem.toState"
@@ -14,8 +17,8 @@
:to="menuItem.toState"
:is-child-menu-active="menuItem.key === activeMenuItem"
/>
</nav>
<div class="menu vertical user-menu">
</div>
<div class="flex flex-col items-center justify-end pb-6">
<primary-nav-item
v-if="!isACustomBrandedInstance"
icon="book-open-globe"
@@ -101,27 +104,3 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.primary--sidebar {
display: flex;
flex-direction: column;
width: var(--space-jumbo);
border-right: 1px solid var(--s-50);
box-sizing: content-box;
height: 100%;
flex-shrink: 0;
}
.menu {
align-items: center;
margin-top: var(--space-medium);
}
.user-menu {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: flex-end;
margin-bottom: var(--space-normal);
}
</style>

Some files were not shown because too many files have changed in this diff Show More