Compare commits

...
Author SHA1 Message Date
Pranav Raj S 46b91ad840 Running version 2024-01-27 23:33:15 -08:00
Pranav Raj S ed8b58ad10 Remove foundation 2024-01-27 23:25:10 -08:00
Pranav Raj S 4928f28c64 Remove ionicons 2024-01-27 23:23:27 -08:00
Pranav Raj S 30539c27f2 Merge branch 'develop' into vite-ruby-migration 2024-01-27 23:21:44 -08:00
Pranav Raj SandGitHub 12916ceca6 fix: Capture delivery errors to avoid false positives (#8790)
The system did not detect the delivery errors earlier, resulting in some false positives. The user was not informed when an email failed to be delivered. While we do handle failure status in other channels, we were not able to capture the actual delivery status for the email channel.

This pull request makes the following changes:

- Updated the class EmailReplyWorker to use the deliver_now method instead of deliver_later. This change is made to raise any errors that may occur with the SMTP connection. The errors are then captured and sent to Sentry, and the email is marked as failed. Previously, we did not consider the case of retries in the email channel, so this feature is currently not functioning. So, I have disabled the retry option. We will address this in a follow-up ticket.
- Downgraded the net-smtp gem to version 0.3.4. This change is made to avoid an argument error when using XOAUTH2.

Fixes: https://linear.app/chatwoot/issue/CW-3032/argumenterror-wrong-authentication-type-xoauth2-argumenterror
2024-01-26 14:22:18 +04:00
Pranav Raj S 2e747e7fa6 Remove webpack 2024-01-25 12:43:39 -08:00
Pranav Raj S a1b6b0c06c Running dashboard, help center 2024-01-25 12:32:50 -08:00
Pranav Raj S 1fe3960285 Upgrade @vuelidate to 2.x 2024-01-25 12:15:21 -08:00
Pranav Raj S c33c844039 Initial Vite Commit 2024-01-25 11:56:22 -08:00
3c952e6a4a fix: change email conversation not found exception to log (#8785)
* fix: change email conversation not found exception to log

* chore: refactor reply_mailbox methods

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-25 22:36:02 +05:30
Sojan JoseandGitHub 59184122f7 fix: [Snyk] Security upgrade administrate from 0.19.0 to 0.20.1 (#8741)
-  Upgrade  administrate  gem to latest
- Update the `show` partial with changes from upstream
2024-01-25 17:33:21 +04:00
381423b1ae fix: Removed author section from public help center (#8767)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-25 17:17:16 +05:30
Pranav Raj SandGitHub b7c9f779ad fix: Avoid processing reactions, ephemeral, request_welcome or unsupported messages (#8780)
Currently, we do not support reactions, ephemeral messages, or the request_welcome event for the WhatsApp channel. However, if this is the first event we receive in Chatwoot (i.e., there is no previous conversation or contact in Chatwoot), it will create a contact and a conversation without any messages. This confuses our customer, as it may appear that Chatwoot has missed some messages. There are multiple cases where this might be the first event we receive in Chatwoot. One quick example is when the user has sent an outbound campaign from another tool and their customers reacted to the message.

Another event like this is request_welcome event. WhatsApp has a concept for welcome messages. You can send an outbound message even though the user has not send a message. You can receive notifications through a webhook whenever a WhatsApp user initiates a chat with you for the first time. (Read the Welcome message section: https://developers.facebook.com/docs/whatsapp/cloud-api/phone-numbers/conversational-components/ ). Although this can help the business send a pro-active message to the user, we don't have it scoped in our feature set. For now, I'm ignoring this event.

Fixes https://linear.app/chatwoot/issue/CW-3018/whatsapp-handle-request-welcome-case-properly
Fixes https://linear.app/chatwoot/issue/CW-3017/whatsapp-handle-reactions-properly
2024-01-25 11:40:18 +04:00
Muhsin KelothandGitHub 904d76420d fix: Add last_activity_at to notification push event data (#8784)
fix: Add last_activity_at to push event data
2024-01-25 12:05:00 +05:30
Muhsin KelothandGitHub fa907840c7 feat: Add middle_name and last_name to contact model (#8771)
feat: Add `middle_name` and `last_name`
2024-01-24 16:22:04 +05:30
3760f206e8 fix: mutex timeout and error handling (#8770)
Fixes the follow cases 
- The ensure block released the lock even on LockAcquisitionError
- Custom timeout was not allowed

This also refactored the with_lock method, now the key has to be constructed in the parent function itself

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-24 14:18:21 +04:00
Sojan JoseandGitHub a861257f73 chore: Fix flaky contacts spec (#8773)
- The ordering was not guaranteed; hence, the specs were failing randomly. Made changes to the expectations accordingly
2024-01-24 13:58:27 +04:00
Muhsin KelothandGitHub 143299f138 feat: Add contact_type attribute to contact model (#8768) 2024-01-24 12:26:47 +05:30
Vishnu NarayananandGitHub 232369cd5c feat: sla 1 - refactor sla_policies model and add applied_sla model (#8602)
* feat: add models

* chore: refactor sla column names

* chore: remove foreign keys

* chore: fix spec

* chore: refactor models
2024-01-23 23:48:02 +05:30
Surabhi SumanandGitHub 4b40c61201 feat: Support Regex validation for custom attributes (#7856)
This allows a user to add/update a custom regex and a cue while defining custom attributes(Only applicable for type- text).
While adding/editing custom attributes, the values are validated against the attribute definition regex, and if it is incorrect, a cue message or default error message is shown and restricts invalid values from being saved.

Fixes: #6866
2024-01-23 18:01:57 +04:00
834c219b9b feat(perf): update query to do a simpler search [CW-2997] (#8763)
Message search would frequently timeout. The reason was that the query would join the conversation too, the new query searches the message table directly

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-23 14:20:00 +04:00
Sivin VargheseandGitHub d0cd1c8887 fix: Help center articles not accessible after authoring agent is deleted (#8756) 2024-01-23 14:06:51 +05:30
Muhsin KelothandGitHub 682a2aea1c chore: Handle twillio Down::ClientError (#8757)
Fixes: https://linear.app/chatwoot/issue/CW-2992/downclienterror-400-bad-request-downclienterror
2024-01-22 15:33:26 +04:00
Shivam MishraandGitHub 1dc66db516 fix: SQL error when rules with missing attributes is triggered (#8673) 2024-01-22 16:09:34 +05:30
Sojan JoseandGitHub bc04d81a5a fix: Handle Net::IMAP::InvalidResponseError Exception bad response type "ESMTP" (#8755) 2024-01-22 15:32:44 +05:30
d2c5c2f9a3 chore: [Snyk] Security upgrade sidekiq from 7.2.0 to 7.2.1 (#8748)
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-01-22 13:12:26 +04:00
Sojan JoseandGitHub 381fda270a chore: Fix typo in Inbox Management copy (#8750)
- Fixes the typo in Inbox Management copy -> vistors to visitors
2024-01-22 13:07:36 +04:00
b3c9d1f1a5 fix: clear timers and animation frame request before component unmounts (#8700)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2024-01-22 12:08:33 +05:30
Sivin VargheseandGitHub fd4376d062 fix: TypeError: Cannot read properties of undefined (reading 'emoji') (#8753) 2024-01-22 12:06:59 +05:30
Pranav Raj SandGitHub a8f053921b fix: Migrate notes when merging the contacts (#8749)
Fixes: https://linear.app/chatwoot/issue/CW-2987/migrate-notes-of-the-secondary-contact-to-primary-contact-when-merging
2024-01-20 10:32:18 +04:00
Sivin VargheseandGitHub 8e15ada164 fix: TypeError: Cannot read properties of undefined (reading 'emoji') (#8747) 2024-01-19 19:55:51 +05:30
Sojan 51093fd543 Merge branch 'release/3.5.2' into develop 2024-01-19 15:15:33 +04:00
Sojan 48e638cc56 Bump version to 3.5.2 2024-01-19 15:14:49 +04:00
Pavel KuzminandGitHub e6dfc159ed fix: change teamid (#8613) 2024-01-19 16:15:28 +05:30
Chatwoot BotandGitHub 900c2de89a chore: Update translations (#8717) 2024-01-19 14:32:27 +04:00
29106ebc4b fix: raise_lock_acquisition_error if the job cannot set the lock (#8744)
Consider a scenario where two jobs are concurrently accessing a job with a lock mechanism. If the second job accesses lock_manager.locked? before the first job called lock_manager.lock(lock_key), it would return a false positive. At this point, both jobs can be executed freely.

To address this issue, the following change ensures that only the current thread that has set the lock can execute. Otherwise, a LockAcquisitionError will be thrown.

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2024-01-19 14:31:41 +04:00
Pranav Raj SandGitHub a579684b34 fix: Update the message-id check to use messageId parsing from mail gem (#8743)
- Update the message ID parsing logic to use the mail gem
- Update the code to improve readability
2024-01-19 14:29:04 +04:00
c29a9ad062 feat: Updates branding logo and name in public portal footer (#8745)
- Use white-label settings for Chatwoot Help Center

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-19 13:37:24 +04:00
Muhsin KelothandGitHub 0ac015ce7a feat: Add a job to clear notifications that were created before 1 month (#8732) 2024-01-19 13:03:23 +05:30
19474e0074 chore: Improve active job error logs for deserialization error (#8742)
- Improve active job error logs for deserialization error 

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-18 19:27:18 +04:00
Sojan JoseandGitHub ce8190dacf fix: API error when using SuperAdmin token (#8739)
- Fixes the issue in release 3.5.0, which causes SuperAdmin tokens to throw error during API calls

Fixes: #8719
2024-01-18 18:49:32 +04:00
Shivam MishraandGitHub aacf326ca1 refactor: remove exception tracker (#8737) 2024-01-18 17:49:27 +05:30
Shivam MishraandGitHub e39c14460b fix: validate url for Dashboard Apps [CW-2979] (#8736) 2024-01-18 17:48:30 +05:30
Sojan JoseandGitHub 5f6e17f307 feat: Use Telegram HTML Parsemode (#8731)
- this ensures that the markdown formatted messages from the Chatwoot dashboard will render consistently in telegram UI for the supported types like bold, italics, links etc
2024-01-18 15:36:36 +04:00
Shivam MishraandGitHub 4bf23adcf5 feat: use short_summary for downloading reports [CW-2962] (#8733) 2024-01-18 16:06:13 +05:30
Pranav Raj SandGitHub 1f4d860d9d fix: Use channel.inbox instead of inbox (#8734) 2024-01-18 00:46:25 -08:00
fdbb3bf4b1 fix: Optimize email fetching logic to fix bandwidth exceeded issues (#8730)
The Inboxes::FetchImapEmailsJob is designed to fetch the entire email object and check if its message id is already in the database. However, this process is resource-intensive and time-consuming, as fetching the full email object takes a significant amount of time.

On average, fetching 100 emails can take approximately 3-4 minutes to complete depending on the IMAP server. Since we are not using server flags to identify which emails have already been fetched (to avoid compatibility issues with flags in different email services), we have to fetch all previously available emails. Currently we fetch all the messages that were created from yesterday. This becomes problematic with services like Gmail, which throttle requests based on bandwidth usage.

To address this issue, I have updated the logic as follows:

Fetch the sequence IDs of all the mails in the "Inbox" that were created from yesterday.
Use the FETCH command to fetch only the message-ids using BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)] with the sequence IDs we got in the previous step. This is a faster operation with lower bandwidth usage, as it only returns the sequence ID and message ID.
Check if the message IDs are already present in the database for the selected inbox.
If not present, fetch the entire email object using the FETCH command and create the message.
If the message ID is already present, ignore it and move on to the next message-id.
I have also addressed the issue of duplicate emails appearing in conversations when two fetch email jobs occur simultaneously. I have added a lock for the channel to ensure that the job gracefully exits without waiting for the current job to complete.

Fixes #7247
Fixes #6082
Fixes #8314

Co-authored-by: Sojan <sojan@pepalo.com>
2024-01-18 11:45:16 +04:00
Muhsin KelothandGitHub c899cc825d fix: Handle Contact import MalformedCSVError (#8706) 2024-01-18 13:05:58 +05:30
Muhsin KelothandGitHub eb972684b3 feat: Show notification last active time instead of created time (#8724) 2024-01-18 09:41:53 +05:30
Muhsin KelothandGitHub 896473f03e fix: Notification count is incorrect when the number of notifications exceeds the page size. (#8723)
- The notification count is incorrect when the number of notifications exceeds the page size.
2024-01-17 15:14:37 +04:00
Sojan 8c43edbd50 Merge branch 'release/3.5.1' into develop 2024-01-17 13:48:28 +04:00
289 changed files with 3164 additions and 14988 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ jobs:
name: yarn
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
# Store yarn / webpacker cache
# Store yarn cache
- save_cache:
key: chatwoot-yarn-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }}
paths:
+4 -4
View File
@@ -3,10 +3,10 @@ sed -i -e '/REDIS_URL/ s/=.*/=redis:\/\/localhost:6379/' .env
sed -i -e '/POSTGRES_HOST/ s/=.*/=localhost/' .env
sed -i -e '/SMTP_ADDRESS/ s/=.*/=localhost/' .env
sed -i -e "/FRONTEND_URL/ s/=.*/=https:\/\/$CODESPACE_NAME-3000.githubpreview.dev/" .env
sed -i -e "/WEBPACKER_DEV_SERVER_PUBLIC/ s/=.*/=https:\/\/$CODESPACE_NAME-3035.githubpreview.dev/" .env
# uncomment the webpacker env variable
sed -i -e '/WEBPACKER_DEV_SERVER_PUBLIC/s/^# //' .env
# fix the error with webpacker
sed -i -e "/DEV_SERVER_PUBLIC/ s/=.*/=https:\/\/$CODESPACE_NAME-3035.githubpreview.dev/" .env
# uncomment the dev server env variable
sed -i -e '/DEV_SERVER_PUBLIC/s/^# //' .env
# fix the error with dev server
echo 'export NODE_OPTIONS=--openssl-legacy-provider' >> ~/.zshrc
# codespaces make the ports public
+1 -1
View File
@@ -223,7 +223,7 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
# if you want to use letter_opener for local emails
# LETTER_OPENER=true
# meant to be used in github codespaces
# WEBPACKER_DEV_SERVER_PUBLIC=
# DEV_SERVER_PUBLIC=
# If you want to use official mobile app,
# the notifications would be relayed via a Chatwoot server
+2 -10
View File
@@ -7,11 +7,10 @@ module.exports = {
'plugin:cypress/recommended',
],
parserOptions: {
parser: '@babel/eslint-parser',
ecmaVersion: 2020,
ecmaVersion: 2022,
sourceType: 'module',
},
plugins: ['html', 'prettier', 'babel'],
plugins: ['html', 'prettier'],
rules: {
'prettier/prettier': ['error'],
camelcase: 'off',
@@ -56,13 +55,6 @@ module.exports = {
'import/extensions': ['off'],
'no-console': 'error',
},
settings: {
'import/resolver': {
webpack: {
config: 'config/webpack/resolve.js',
},
},
},
env: {
browser: true,
jest: true,
+9 -1
View File
@@ -75,4 +75,12 @@ yalc.lock
yarn-debug.log*
.yarn-integrity
/storybook-static
/storybook-static
# Vite Ruby
/public/vite*
node_modules
# Vite uses dotenv and suggests to ignore local-only env files. See
# https://vitejs.dev/guide/env-and-mode.html#env-files
*.local
+1 -3
View File
@@ -2,7 +2,6 @@ import { addDecorator } from '@storybook/vue';
import Vue from 'vue';
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';
@@ -14,7 +13,6 @@ import { domPurifyConfig } from 'shared/helpers/HTMLSanitizer';
import '../app/javascript/dashboard/assets/scss/storybook.scss';
Vue.use(VueI18n);
Vue.use(Vuelidate);
Vue.use(WootUiKit);
Vue.use(Vuex);
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
@@ -32,7 +30,7 @@ addDecorator(() => ({
template: '<story/>',
i18n: i18nConfig,
store,
beforeCreate: function() {
beforeCreate: function () {
this.$root._i18n = this.$i18n;
},
}));
+5 -3
View File
@@ -39,6 +39,8 @@ gem 'rack-attack', '>= 6.7.0'
gem 'down'
# authentication type to fetch and send mail over oauth2.0
gem 'gmail_xoauth'
# Lock net-smtp to 0.3.4 to avoid issues with gmail_xoauth2
gem 'net-smtp', '~> 0.3.4'
# Prevent CSV injection
gem 'csv-safe'
@@ -62,7 +64,7 @@ gem 'activerecord-import'
gem 'dotenv-rails'
gem 'foreman'
gem 'puma'
gem 'webpacker'
gem 'vite_rails'
# metrics on heroku
gem 'barnes'
@@ -74,7 +76,7 @@ gem 'devise_token_auth'
gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate', '>= 0.19.0'
gem 'administrate', '>= 0.20.1'
gem 'administrate-field-active_storage', '>= 1.0.1'
gem 'administrate-field-belongs_to_search', '>= 0.9.0'
@@ -114,7 +116,7 @@ gem 'sentry-ruby', require: false
gem 'sentry-sidekiq', '>= 5.14.0', require: false
##-- background job processing --##
gem 'sidekiq', '>= 7.1.3'
gem 'sidekiq', '>= 7.2.1'
# We want cron jobs
gem 'sidekiq-cron', '>= 1.12.0'
+25 -22
View File
@@ -105,12 +105,12 @@ GEM
activerecord (>= 6.0, < 7.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
administrate (0.19.0)
actionpack (>= 5.0)
actionview (>= 5.0)
activerecord (>= 5.0)
jquery-rails (>= 4.0)
kaminari (>= 1.0)
administrate (0.20.1)
actionpack (>= 6.0, < 8.0)
actionview (>= 6.0, < 8.0)
activerecord (>= 6.0, < 8.0)
jquery-rails (~> 4.6.0)
kaminari (~> 1.2.2)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
administrate-field-active_storage (1.0.1)
@@ -169,7 +169,7 @@ GEM
climate_control (1.2.0)
coderay (1.1.3)
commonmarker (0.23.10)
concurrent-ruby (1.2.2)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crack (0.4.5)
rexml
@@ -216,6 +216,7 @@ GEM
railties (>= 3.2)
down (5.4.0)
addressable (~> 2.8)
dry-cli (1.0.0)
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
elastic-apm (4.6.2)
@@ -461,7 +462,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.20.0)
minitest (5.21.2)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
@@ -479,7 +480,7 @@ GEM
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.4.0)
net-smtp (0.3.4)
net-protocol
netrc (0.11.0)
newrelic-sidekiq-metrics (1.6.2)
@@ -570,7 +571,7 @@ GEM
rack (>= 1.2.0)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rack-proxy (0.7.6)
rack-proxy (0.7.7)
rack
rack-test (2.1.0)
rack (>= 1.3)
@@ -610,7 +611,7 @@ GEM
ffi (~> 1.0)
redis (5.0.6)
redis-client (>= 0.9.0)
redis-client (0.19.0)
redis-client (0.19.1)
connection_pool
redis-namespace (1.10.0)
redis (>= 4)
@@ -708,7 +709,6 @@ GEM
activerecord (>= 4)
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.14.0)
railties (>= 5.0)
sentry-ruby (~> 5.14.0)
@@ -720,11 +720,11 @@ GEM
sexp_processor (4.17.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (7.2.0)
sidekiq (7.2.1)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
redis-client (>= 0.14.0)
redis-client (>= 0.19.0)
sidekiq-cron (1.12.0)
fugit (~> 1.8)
globalid (>= 1.0.1)
@@ -795,6 +795,13 @@ GEM
activemodel (>= 3.2)
mail (~> 2.5)
version_gem (1.1.2)
vite_rails (3.0.17)
railties (>= 5.1, < 8)
vite_ruby (~> 3.0, >= 3.2.2)
vite_ruby (3.5.0)
dry-cli (>= 0.7, < 2)
rack-proxy (~> 0.6, >= 0.6.1)
zeitwerk (~> 2.2)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@@ -810,11 +817,6 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.4.4)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.8.1)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
@@ -840,7 +842,7 @@ DEPENDENCIES
active_record_query_trace
activerecord-import
acts-as-taggable-on
administrate (>= 0.19.0)
administrate (>= 0.20.1)
administrate-field-active_storage (>= 1.0.1)
administrate-field-belongs_to_search (>= 0.9.0)
annotate
@@ -903,6 +905,7 @@ DEPENDENCIES
meta_request
mock_redis
neighbor
net-smtp (~> 0.3.4)
newrelic-sidekiq-metrics (>= 1.6.2)
newrelic_rpm
omniauth (>= 2.1.2)
@@ -939,7 +942,7 @@ DEPENDENCIES
sentry-ruby
sentry-sidekiq (>= 5.14.0)
shoulda-matchers
sidekiq (>= 7.1.3)
sidekiq (>= 7.2.1)
sidekiq-cron (>= 1.12.0)
simplecov (= 0.17.1)
slack-ruby-client (~> 2.2.0)
@@ -956,10 +959,10 @@ DEPENDENCIES
tzinfo-data
uglifier
valid_email2
vite_rails
web-console (>= 4.2.1)
web-push
webmock
webpacker
wisper (= 2.0.0)
working_hours
+1 -1
View File
@@ -1,4 +1,4 @@
backend: bin/rails s -p 3000
frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml
frontend: bin/vite dev
+1 -1
View File
@@ -1,3 +1,3 @@
backend: RAILS_ENV=test bin/rails s -p 5050
frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
frontend: bin/vite
worker: RAILS_ENV=test dotenv bundle exec sidekiq -C config/sidekiq.yml
+5
View File
@@ -12,6 +12,7 @@ class ContactMergeAction
merge_conversations
merge_messages
merge_contact_inboxes
merge_contact_notes
merge_and_remove_mergee_contact
end
@base_contact
@@ -33,6 +34,10 @@ class ContactMergeAction
Conversation.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
end
def merge_contact_notes
Note.where(contact_id: @mergee_contact.id, account_id: @mergee_contact.account_id).update(contact_id: @base_contact.id)
end
def merge_messages
Message.where(sender: @mergee_contact).update(sender: @base_contact)
end
@@ -86,8 +86,5 @@ $swift-ease-out-duration: .4s !default;
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default;
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions
$transition-ease-in: all 0.250s ease-in;
+8
View File
@@ -46,6 +46,14 @@ class V2::ReportBuilder
}
end
def short_summary
{
conversations_count: conversations.count,
avg_first_response_time: avg_first_response_time_summary,
avg_resolution_time: avg_resolution_time_summary
}
end
def conversation_metrics
if params[:type].equal?(:account)
live_conversations
@@ -39,6 +39,8 @@ class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Account
:attribute_display_type,
:attribute_key,
:attribute_model,
:regex_pattern,
:regex_cue,
attribute_values: []
)
end
@@ -9,7 +9,7 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
def index
@unread_count = notification_finder.unread_count
@notifications = notification_finder.perform
@count = @notifications.count
@count = notification_finder.count
end
def read_all
@@ -14,7 +14,14 @@ module AccessTokenAuthHelper
render_unauthorized('Invalid Access Token') && return if @access_token.blank?
@resource = @access_token.owner
Current.user = @resource if [User, AgentBot].include?(@resource.class)
Current.user = @resource if allowed_current_user_type?(@resource)
end
def allowed_current_user_type?(resource)
return true if resource.is_a?(User)
return true if resource.is_a?(AgentBot)
false
end
def validate_bot_access_token!
@@ -1,6 +1,7 @@
class Public::Api::V1::Portals::BaseController < PublicController
before_action :show_plain_layout
before_action :set_color_scheme
before_action :set_global_config
around_action :set_locale
after_action :allow_iframe_requests
@@ -60,4 +61,8 @@ class Public::Api::V1::Portals::BaseController < PublicController
portal
render 'public/api/v1/portals/error/404', status: :not_found
end
def set_global_config
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'BRAND_URL')
end
end
+1 -1
View File
@@ -53,7 +53,7 @@ module Api::V1::InboxesHelper
rescue StandardError => e
raise StandardError, e.message
ensure
ChatwootExceptionTracker.new(e).capture_exception if e.present?
Rails.logger.error "[Api::V1::InboxesHelper] check_imap_connection failed with #{e.message}" if e.present?
end
def check_smtp_connection(channel_data, smtp)
@@ -37,7 +37,7 @@ module Api::V2::Accounts::ReportsHelper
business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours])
}
)
).summary
).short_summary
end
private
@@ -1,4 +1,4 @@
@import '~vue2-datepicker/scss/index';
@import 'vue2-datepicker/scss/index';
.date-picker {
&.no-margin {
@@ -1,4 +1,4 @@
@import '~dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/variables';
.formulate-input {
.formulate-input-errors {
@@ -1,623 +0,0 @@
// Foundation for Sites Settings
// -----------------------------
//
// Table of Contents:
//
// 1. Global
// 2. Breakpoints
// 3. The Grid
// 4. Base Typography
// 5. Typography Helpers
// 6. Abide
// 7. Accordion
// 8. Accordion Menu
// 9. Badge
// 10. Breadcrumbs
// 11. Button
// 12. Button Group
// 13. Callout
// 14. Card
// 15. Close Button
// 16. Drilldown
// 17. Dropdown
// 18. Dropdown Menu
// 19. Forms
// 20. Label
// 21. Media Object
// 22. Menu
// 23. Meter
// 24. Off-canvas
// 25. Orbit
// 26. Pagination
// 27. Progress Bar
// 28. Responsive Embed
// 29. Reveal
// 30. Slider
// 31. Switch
// 32. Table
// 33. Tabs
// 34. Thumbnail
// 35. Title Bar
// 36. Tooltip
// 37. Top Bar
@import '~foundation-sites/scss/util/util';
// 1. Global
// ---------
// Disable contrast warnings in Foundation.
$contrast-warnings: false;
$global-font-size: 16px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (primary: $color-woot,
secondary: #5d7592,
success: #44ce4b,
warning: #ffc532,
alert: #ff382d);
$light-gray: #c0ccda;
$medium-gray: #8492a6;
$dark-gray: $color-gray;
$black: #000;
$white: #fff;
$body-background: $white;
$body-font-color: $color-body;
$body-font-family: 'PlusJakarta',
-apple-system,
system-ui,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Tahoma,
Arial,
sans-serif;
$body-antialiased: true;
$global-margin: $space-small;
$global-padding: $space-small;
$global-weight-normal: normal;
$global-weight-bold: bold;
$global-radius: 0;
$global-text-direction: ltr;
$global-flexbox: false;
$print-transparent-backgrounds: true;
@include add-foundation-colors;
// 2. Breakpoints
// --------------
$breakpoints: (small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1400px,
xxxlarge: 1600px,
);
$print-breakpoint: large;
$breakpoint-classes: (small medium large);
// 3. The Grid
// -----------
$grid-row-width: $global-width;
$grid-column-count: 12;
$grid-column-gutter: (small: $zero,
medium: $zero);
$grid-column-align-edge: true;
$block-grid-max: 8;
// 4. Base Typography
// ------------------
$header-font-family: $body-font-family;
$header-font-weight: $font-weight-medium;
$header-font-style: normal;
$font-family-monospace: $body-font-family;
$header-color: $color-heading;
$header-lineheight: 1.4;
$header-margin-bottom: 0.3125rem;
$header-styles: (small: ("h1": ("font-size": 24),
"h2": ("font-size": 20),
"h3": ("font-size": 19),
"h4": ("font-size": 18),
"h5": ("font-size": 17),
"h6": ("font-size": 16)),
medium: ("h1": ("font-size": 48),
"h2": ("font-size": 40),
"h3": ("font-size": 31),
"h4": ("font-size": 25),
"h5": ("font-size": 20),
"h6": ("font-size": 16)));
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.65;
$paragraph-margin-bottom: var(--space-small);
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
$code-font-family: $font-family-monospace;
$code-font-weight: $global-weight-normal;
$code-background: $light-gray;
$code-border: 1px solid $medium-gray;
$code-padding: rem-calc(2 5 1);
$anchor-color: $primary-color;
$anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
$anchor-text-decoration: none;
$anchor-text-decoration-hover: none;
$hr-width: $global-width;
$hr-border: 1px solid $medium-gray;
$hr-margin: rem-calc(20) auto;
$list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$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.1875rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
$cite-font-size: rem-calc(13);
$cite-color: $dark-gray;
$cite-pseudo-content: '\2014 \0020';
$keystroke-font: $font-family-monospace;
$keystroke-color: $black;
$keystroke-background: $light-gray;
$keystroke-padding: rem-calc(2 4 0);
$keystroke-radius: $global-radius;
$abbr-underline: 1px dotted $black;
// 5. Typography Helpers
// ---------------------
$lead-font-size: $global-font-size * 1.25;
$lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.125rem;
$subheader-margin-bottom: 0.3125rem;
$stat-font-size: 1.5625rem;
// 6. Abide
// --------
$abide-inputs: true;
$abide-labels: true;
$input-background-invalid: get-color(alert);
$form-label-color-invalid: get-color(alert);
$input-error-color: get-color(alert);
$input-error-font-size: rem-calc(12);
$input-error-font-weight: $global-weight-bold;
// 7. Accordion
// ------------
$accordion-background: $white;
$accordion-plusminus: true;
$accordion-title-font-size: rem-calc(12);
$accordion-item-color: $primary-color;
$accordion-item-background-hover: $light-gray;
$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: 0.625rem;
// 8. Accordion Menu
// -----------------
$accordionmenu-arrows: true;
$accordionmenu-arrow-color: $primary-color;
$accordionmenu-arrow-size: 6px;
// 9. Badge
// --------
$badge-background: $primary-color;
$badge-color: $white;
$badge-color-alt: $black;
$badge-palette: $foundation-palette;
$badge-padding: var(--space-smaller);
$badge-minwidth: 2.1em;
$badge-font-size: var(--font-size-nano);
// 10. Breadcrumbs
// ---------------
$breadcrumbs-margin: 0 0 $global-margin 0;
$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.46875rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
// 11. Button
// ----------
$button-padding: var(--space-smaller) 1em;
$button-margin: 0 0 $global-margin 0;
$button-fill: solid;
$button-background: $primary-color;
$button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $white;
$button-radius: var(--border-radius-normal);
$button-sizes: (tiny: var(--font-size-micro),
small: var(--font-size-mini),
default: var(--font-size-small),
large: var(--font-size-medium));
$button-palette: $foundation-palette;
$button-opacity-disabled: 0.4;
$button-background-hover-lightness: -20%;
$button-hollow-hover-lightness: -50%;
$button-transition: background-color 0.25s ease-out,
color 0.25s ease-out;
// 12. Button Group
// ----------------
$buttongroup-margin: 0;
$buttongroup-spacing: 0;
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
$buttongroup-radius-on-each: false;
// 13. Callout
// -----------
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$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;
$callout-link-tint: 30%;
// 14. Card
// --------
$card-background: $white;
$card-font-color: $body-font-color;
$card-divider-background: $light-gray;
$card-border: 1px solid var(--color-border);
$card-shadow: var(--shadow-small);
$card-border-radius: var(--border-radius-normal);
$card-padding: var(--space-small);
$card-margin: $global-margin;
// 15. Close Button
// ----------------
$closebutton-position: right top;
$closebutton-offset-horizontal: (small: 0.66rem,
medium: 1rem);
$closebutton-offset-vertical: (small: 0.33em,
medium: 0.5rem);
$closebutton-size: (small: 1.5em,
medium: 2em);
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
// 16. Drilldown
// -------------
$drilldown-transition: transform 0.15s linear;
$drilldown-arrows: true;
$drilldown-arrow-color: $primary-color;
$drilldown-arrow-size: 6px;
$drilldown-background: $white;
// 17. Dropdown
// ------------
$dropdown-padding: 0.625rem;
$dropdown-background: $body-background;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 0.625rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (tiny: 100px,
small: 200px,
large: 400px);
// 18. Dropdown Menu
// -----------------
$dropdownmenu-arrows: true;
$dropdownmenu-arrow-color: $anchor-color;
$dropdownmenu-arrow-size: 6px;
$dropdownmenu-min-width: 200px;
$dropdownmenu-background: $white;
$dropdownmenu-border: 1px solid $medium-gray;
// 19. Forms
// ---------
$fieldset-border: 1px solid $light-gray;
$fieldset-padding: $space-two;
$fieldset-margin: $space-one $zero;
$legend-padding: rem-calc(0 3);
$form-spacing: $space-normal;
$helptext-color: $color-body;
$helptext-font-size: $font-size-small;
$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: 0.625rem;
$form-label-color: $color-body;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $font-weight-medium;
$form-label-line-height: 1.8;
$select-background: $white;
$select-triangle-color: $dark-gray;
$select-radius: var(--border-radius-normal);
$input-color: $color-body;
$input-placeholder-color: $light-gray;
$input-font-family: inherit;
$input-font-size: $font-size-default;
$input-font-weight: $global-weight-normal;
$input-background: $white;
$input-background-focus: $white;
$input-background-disabled: $light-gray;
$input-border: 1px solid var(--s-200);
$input-border-focus: 1px solid lighten($primary-color, 15%);
$input-shadow: 0;
$input-shadow-focus: 0;
$input-cursor-disabled: not-allowed;
$input-transition: border-color 0.25s ease-in-out;
$input-number-spinners: true;
$input-radius: var(--border-radius-normal);
$form-button-radius: var(--border-radius-normal);
// 20. Label
// ---------
$label-background: $white;
$label-color: $black;
$label-color-alt: $black;
$label-palette: $foundation-palette;
$label-font-size: $font-size-mini;
$label-padding: $space-smaller $space-small;
$label-radius: var(--border-radius-small);
// 21. Media Object
// ----------------
$mediaobject-margin-bottom: $global-margin;
$mediaobject-section-padding: $global-padding;
$mediaobject-image-width-stacked: 100%;
// 22. Menu
// --------
$menu-margin: 0;
$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.15625rem;
$menu-item-background-hover: $light-gray;
$menu-border: $light-gray;
// 23. Meter
// ---------
$meter-height: 0.625rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
$meter-fill-medium: $warning-color;
$meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-vertical-sizes: (small: 14.375,
medium: 14.375,
);
$offcanvas-background: $light-gray;
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
$offcanvas-push-zindex: 1;
$offcanvas-overlap-zindex: 10;
$offcanvas-reveal-zindex: 1;
$offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: 'off-canvas-content';
// 25. Orbit
// ---------
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$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: 0.625rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 0.625rem;
$orbit-control-zindex: 10;
// 26. Pagination
// --------------
$pagination-font-size: rem-calc(14);
$pagination-margin-bottom: $global-margin;
$pagination-item-color: $black;
$pagination-item-padding: rem-calc(3 10);
$pagination-item-spacing: rem-calc(1);
$pagination-radius: $global-radius;
$pagination-item-background-hover: $light-gray;
$pagination-item-background-current: $primary-color;
$pagination-item-color-current: $white;
$pagination-item-color-disabled: $medium-gray;
$pagination-ellipsis-color: $black;
$pagination-mobile-items: false;
$pagination-mobile-current-item: false;
$pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 0.625rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
$progress-radius: $global-radius;
// 28. Responsive Embed
// --------------------
$responsive-embed-margin-bottom: rem-calc(16);
$responsive-embed-ratios: (default: 4 by 3,
widescreen: 16 by 9);
// 29. Reveal
// ----------
$reveal-background: $white;
$reveal-width: 600px;
$reveal-max-width: $global-width;
$reveal-padding: $global-padding;
$reveal-border: 1px solid $medium-gray;
$reveal-radius: $global-radius;
$reveal-zindex: 1005;
$reveal-overlay-background: rgba($black, 0.45);
// 30. Slider
// ----------
$slider-width-vertical: 0.3125rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.3125rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 0.875rem;
$slider-handle-width: 0.875rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
// 31. Switch
// ----------
$switch-background: $light-gray;
$switch-background-active: $primary-color;
$switch-height: $space-two;
$switch-height-tiny: $space-slab;
$switch-height-small: $space-normal;
$switch-height-large: $space-large;
$switch-radius: $space-large;
$switch-margin: $global-margin;
$switch-paddle-background: $white;
$switch-paddle-offset: $space-micro;
$switch-paddle-radius: $space-large;
$switch-paddle-transition: all 0.15s ease-out;
// 32. Table
// ---------
$table-background: transparent;
$table-color-scale: 5%;
$table-border: 1px solid transparent;
$table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken($table-background,
$table-color-scale + $table-hover-scale);
$table-is-striped: false;
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
$table-head-background: smart-scale($table-background, $table-color-scale / 2);
$table-head-row-hover: darken($table-head-background, $table-hover-scale);
$table-foot-background: smart-scale($table-background, $table-color-scale);
$table-foot-row-hover: darken($table-foot-background, $table-hover-scale);
$table-head-font-color: $body-font-color;
$table-foot-font-color: $body-font-color;
$show-header-for-stacked: false;
// 33. Tabs
// --------
$tab-margin: 0;
$tab-background: transparent;
$tab-background-active: transparent;
$tab-item-font-size: $font-size-small;
$tab-item-background-hover: transparent;
$tab-item-padding: $space-one $zero;
$tab-color: $primary-color;
$tab-active-color: $primary-color;
$tab-expand-max: 6;
$tab-content-background: transparent;
$tab-content-border: transparent;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 0.625rem;
// 34. Thumbnail
// -------------
$thumbnail-border: solid 4px $white;
$thumbnail-margin-bottom: $global-margin;
$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
$thumbnail-transition: box-shadow 200ms ease-out;
$thumbnail-radius: $global-radius;
// 35. Title Bar
// -------------
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.3125rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.15625rem;
// 36. Tooltip
// -----------
$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.46875rem;
$tooltip-font-size: $font-size-mini;
$tooltip-pip-width: 0.46875rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 37. Top Bar
// -----------
$topbar-padding: 0.3125;
$topbar-background: $light-gray;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 0.3125 0.625rem 0.3125 0;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;
// Internal variable that contains the flex justifying options
$-zf-flex-justify: -zf-flex-justify($global-text-direction);
$menu-items-padding: $space-one;
$xy-grid: false;
@@ -2,11 +2,6 @@
@apply bg-slate-25 dark:bg-slate-800;
}
.flex-center {
@include flex-align(center, middle);
display: flex;
}
.bottom-space-fix {
margin-bottom: auto;
}
@@ -1,5 +1,5 @@
@import '~dashboard/assets/scss/variables';
@import '~widget/assets/scss/mixins';
@import 'dashboard/assets/scss/variables';
@import 'widget/assets/scss/mixins';
$spinner-before-border-color: rgba(255, 255, 255, 0.7);
@@ -89,9 +89,6 @@ $swift-ease-out-duration: .4s !default;
$swift-ease-out-function: cubic-bezier(0.37, 0, 0.63, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-function !default;
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions
$transition-ease-in: all 0.250s ease-in;
@@ -15,36 +15,13 @@
@import 'variables';
@import 'mixins';
@import 'foundation-settings';
@import 'helper-classes';
@import 'formulate';
@import 'date-picker';
@import 'foundation-sites/scss/foundation';
@include foundation-everything($flex: true);
@include foundation-prototype-text-utilities;
@include foundation-prototype-text-transformation;
@include foundation-prototype-text-decoration;
@include foundation-prototype-font-styling;
@include foundation-prototype-list-style-type;
@include foundation-prototype-rounded;
@include foundation-prototype-bordered;
@include foundation-prototype-shadow;
@include foundation-prototype-separator;
@include foundation-prototype-overflow;
@include foundation-prototype-display;
@include foundation-prototype-position;
@include foundation-prototype-border-box;
@include foundation-prototype-border-none;
@include foundation-prototype-sizing;
@include foundation-prototype-spacing;
@import 'typography';
@import 'layout';
@import 'animations';
@import 'foundation-custom';
@import 'rtl';
@import 'widgets/buttons';
@@ -52,15 +29,11 @@
@import 'widgets/conversation-card';
@import 'widgets/conversation-view';
@import 'widgets/forms';
@import 'widgets/login';
@import 'widgets/modal';
@import 'widgets/reply-box';
@import 'widgets/report';
@import 'widgets/search-box';
@import 'widgets/sidemenu';
@import 'widgets/snackbar';
@import 'widgets/states';
@import 'widgets/status-bar';
@import 'widgets/tabs';
@import 'widgets/woot-tables';
@@ -69,7 +42,6 @@
@import 'plugins/multiselect';
@import 'plugins/dropdown';
@import '~shared/assets/stylesheets/ionicons';
@import 'utility-helpers';
.tooltip {
@@ -10,21 +10,14 @@
@import 'variables';
@import 'vue-multiselect/dist/vue-multiselect.min.css';
@import '~shared/assets/stylesheets/ionicons';
@import 'mixins';
@import 'foundation-settings';
@import 'helper-classes';
@import 'foundation-sites/scss/foundation';
@include foundation-prototype-spacing;
@include foundation-everything($flex: true);
@import 'typography';
@import 'layout';
@import 'animations';
@import 'foundation-custom';
@import 'widgets/buttons';
@import 'widgets/forms';
@@ -1,66 +0,0 @@
.auth-wrap {
width: 100%;
}
// Outside login wrapper
.login {
@include full-height;
overflow-y: auto;
padding-top: $space-larger * 1.2;
.login__hero {
margin-bottom: $space-larger;
.hero__logo {
width: 180px;
}
.hero__title {
font-weight: $font-weight-light;
margin-top: $space-larger;
}
.hero__sub {
color: $medium-gray;
font-size: $font-size-medium;
}
}
// Login box
.login-box {
@include background-white;
@include border-normal;
@include elegant-card;
border-radius: $space-smaller;
padding: $space-large;
label {
color: $color-gray;
font-size: $font-size-default;
input {
font-size: $font-size-default;
height: $space-larger;
padding: $space-slab;
}
.error {
font-size: $font-size-small;
}
}
.button {
height: $space-larger;
}
}
.sigin__footer {
font-size: $font-size-default;
padding: $space-medium;
> a {
font-weight: $font-weight-bold;
}
}
}
@@ -1,7 +1,5 @@
.modal-mask {
// @include flex;
// @include flex-align(center, middle);
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full;
@apply flex items-center justify-center bg-modal-backdrop-light dark:bg-modal-backdrop-dark z-[9990] h-full left-0 fixed top-0 w-full;
}
.page-top-bar {
@@ -45,8 +43,6 @@
}
.modal-footer {
// @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 {
@@ -8,7 +8,7 @@
.reply-box__top {
.icon {
color: $medium-gray;
color: var(--slate-500);
cursor: pointer;
font-size: $font-size-medium;
margin-right: $space-small;
@@ -34,7 +34,7 @@
resize: none;
}
> textarea {
>textarea {
@include ghost-input();
background: transparent;
margin: 0;
@@ -52,7 +52,7 @@
.reply-box__top {
@apply bg-yellow-100 dark:bg-yellow-800;
> input {
>input {
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
@@ -1,45 +1,10 @@
.side-menu {
i {
margin-right: var(--space-smaller);
min-width: var(--space-two);
}
}
.sidebar {
z-index: 1024 - 1;
//logo
.logo {
img {
max-height: 108px;
padding: $woot-logo-padding;
}
}
.nested {
a {
font-size: var(--font-size-small);
margin-bottom: var(--space-micro);
margin-top: var(--space-micro);
.inbox-icon {
display: inline-block;
margin-right: var(--space-micro);
min-width: var(--space-normal);
text-align: center;
}
}
}
}
// bottom-nav
.bottom-nav {
@include flex;
@include space-between-column;
@include border-normal-top;
flex-direction: column;
padding: var(--space-one) var(--space-normal) var(--space-one)
var(--space-one);
padding: var(--space-one) var(--space-normal) var(--space-one) var(--space-one);
position: relative;
&:hover {
@@ -519,6 +519,9 @@ export default {
},
},
watch: {
teamId() {
this.updateVirtualListProps('teamId', this.teamId);
},
activeTeam() {
this.resetAndFetchData();
},
@@ -13,7 +13,7 @@
<div class="flex items-center justify-between w-full">
<span
class="attribute-name"
:class="{ error: $v.editedValue.$error }"
:class="{ error: v$.editedValue.$error }"
>
{{ label }}
</span>
@@ -39,8 +39,8 @@
:type="inputType"
class="input-group-field"
autofocus="true"
:class="{ error: $v.editedValue.$error }"
@blur="$v.editedValue.$touch"
:class="{ error: v$.editedValue.$error }"
@blur="v$.editedValue.$touch"
@keyup.enter="onUpdate"
/>
<div class="input-group-button">
@@ -122,25 +122,37 @@
<script>
import { format, parseISO } from 'date-fns';
import { required, url } from 'vuelidate/lib/validators';
import { required, url } from '@vuelidate/validators';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import { isValidURL } from '../helper/URLHelper';
import customAttributeMixin from '../mixins/customAttributeMixin';
const DATE_FORMAT = 'yyyy-MM-dd';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
MultiselectDropdown,
},
mixins: [customAttributeMixin],
props: {
label: { type: String, required: true },
values: { type: Array, default: () => [] },
value: { type: [String, Number, Boolean], default: '' },
showActions: { type: Boolean, default: false },
attributeType: { type: String, default: 'text' },
attributeRegex: {
type: String,
default: null,
},
regexCue: { type: String, default: null },
regexEnabled: { type: Boolean, default: false },
attributeKey: { type: String, required: true },
contactId: { type: Number, default: null },
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
isEditing: false,
@@ -198,12 +210,17 @@ export default {
return this.isAttributeTypeLink ? 'url' : this.attributeType;
},
shouldShowErrorMessage() {
return this.$v.editedValue.$error;
return this.v$.editedValue.$error;
},
errorMessage() {
if (this.$v.editedValue.url) {
if (this.v$.editedValue.url) {
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_URL');
}
if (!this.v$.editedValue.regexValidation) {
return this.regexCue
? this.regexCue
: this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_INPUT');
}
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.REQUIRED');
},
},
@@ -221,7 +238,15 @@ export default {
};
}
return {
editedValue: { required },
editedValue: {
required,
regexValidation: value => {
return !(
this.attributeRegex &&
!this.getRegexp(this.attributeRegex).test(value)
);
},
},
};
},
mounted() {
@@ -259,8 +284,8 @@ export default {
this.attributeType === 'date'
? parseISO(this.editedValue)
: this.editedValue;
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
this.isEditing = false;
@@ -281,11 +306,14 @@ export default {
.checkbox-wrap {
@apply flex items-center;
}
.checkbox {
@apply my-0 mr-2 ml-0;
}
.attribute-name {
@apply w-full text-slate-800 dark:text-slate-100;
&.error {
@apply text-red-400 dark:text-red-500;
}
@@ -302,6 +330,7 @@ export default {
.value {
@apply bg-slate-50 dark:bg-slate-700 mb-0;
}
.edit-button {
@apply block;
}
@@ -311,10 +340,12 @@ export default {
::v-deep {
.selector-wrap {
@apply m-0 top-1;
.selector-name {
@apply ml-0;
}
}
.name {
@apply ml-0;
}
+15 -15
View File
@@ -1,29 +1,29 @@
/* eslint no-plusplus: 0 */
import AvatarUploader from './widgets/forms/AvatarUploader.vue';
import Bar from './widgets/chart/BarChart';
import Button from './ui/WootButton';
import Code from './Code';
import ColorPicker from './widgets/ColorPicker';
import Button from './ui/WootButton.vue';
import Code from './Code.vue';
import ColorPicker from './widgets/ColorPicker.vue';
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
import ContextMenu from './ui/ContextMenu.vue';
import DeleteModal from './widgets/modal/DeleteModal.vue';
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
import FeatureToggle from './widgets/FeatureToggle';
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import FeatureToggle from './widgets/FeatureToggle.vue';
import HorizontalBar from './widgets/chart/HorizontalBarChart';
import Input from './widgets/forms/Input.vue';
import PhoneInput from './widgets/forms/PhoneInput.vue';
import Label from './ui/Label';
import LoadingState from './widgets/LoadingState';
import Modal from './Modal';
import ModalHeader from './ModalHeader';
import ReportStatsCard from './widgets/ReportStatsCard';
import SidemenuIcon from './SidemenuIcon';
import Spinner from 'shared/components/Spinner';
import SubmitButton from './buttons/FormSubmitButton';
import Label from './ui/Label.vue';
import LoadingState from './widgets/LoadingState.vue';
import Modal from './Modal.vue';
import ModalHeader from './ModalHeader.vue';
import ReportStatsCard from './widgets/ReportStatsCard.vue';
import SidemenuIcon from './SidemenuIcon.vue';
import Spinner from 'shared/components/Spinner.vue';
import SubmitButton from './buttons/FormSubmitButton.vue';
import Tabs from './ui/Tabs/Tabs';
import TabsItem from './ui/Tabs/TabsItem';
import TabsItem from './ui/Tabs/TabsItem.vue';
import Thumbnail from './widgets/Thumbnail.vue';
const WootUIKit = {
@@ -19,13 +19,13 @@
<form class="flex flex-col w-full" @submit.prevent="addAccount">
<div class="w-full">
<label :class="{ error: $v.accountName.$error }">
<label :class="{ error: v$.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
v-model.trim="accountName"
type="text"
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
@input="$v.accountName.$touch"
@input="v$.accountName.$touch"
/>
</label>
</div>
@@ -33,8 +33,8 @@
<div class="w-full">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
$v.accountName.$invalid ||
v$.accountName.$invalid ||
v$.accountName.$invalid ||
uiFlags.isCreating
"
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
@@ -49,9 +49,10 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
@@ -65,6 +66,9 @@ export default {
default: true,
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
accountName: '',
@@ -56,7 +56,7 @@
class="bg-slate-50 dark:bg-slate-700 rounded text-xxs font-medium mx-1 py-0 px-1"
:class="
isCountZero
? `text-slate-300 dark:text-slate-700`
? `text-slate-300 dark:text-slate-500`
: `text-slate-700 dark:text-slate-50`
"
>
@@ -1,4 +1,6 @@
export default {
import { defineComponent } from 'vue';
export default defineComponent({
name: 'WootTabs',
props: {
index: {
@@ -73,19 +75,25 @@ export default {
});
const leftButton = this.createScrollButton(createElement, 'left');
const rightButton = this.createScrollButton(createElement, 'right');
return (
<div
class={{
return createElement(
'div',
{
class: {
'tabs--container--with-border': this.border,
'tabs--container': true,
}}
>
{leftButton}
<ul class={{ tabs: true, 'tabs--with-scroll': this.hasScroll }}>
{Tabs}
</ul>
{rightButton}
</div>
},
},
[
leftButton,
createElement(
'ul',
{
class: { tabs: true, 'tabs--with-scroll': this.hasScroll },
},
Tabs
),
rightButton,
]
);
},
};
});
@@ -12,11 +12,11 @@
<woot-input
v-model="value"
type="text"
:class="{ error: $v.value.$error }"
:class="{ error: v$.value.$error }"
:placeholder="
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
"
@blur="$v.value.$touch"
@blur="v$.value.$touch"
/>
</div>
<div class="flex flex-row justify-between gap-2 py-2 px-0 w-full">
@@ -27,7 +27,7 @@
<woot-button variant="clear" @click.prevent="onDismiss">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
</woot-button>
<woot-button :is-disabled="$v.value.$invalid">
<woot-button :is-disabled="v$.value.$invalid">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
</woot-button>
</div>
@@ -37,15 +37,19 @@
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import aiMixin from 'dashboard/mixins/aiMixin';
import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [aiMixin, alertMixin, uiSettingsMixin],
setup() {
return { v$: useVuelidate() };
},
data() {
return {
value: '',
@@ -53,8 +53,8 @@ export default {
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/variables';
@import '~dashboard/assets/scss/mixins';
@import 'dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/mixins';
.colorpicker {
position: relative;
@@ -705,10 +705,11 @@ export default {
</script>
<style lang="scss">
@import '~@chatwoot/prosemirror-schema/src/styles/base.scss';
@import '@chatwoot/prosemirror-schema/src/styles/base.scss';
.ProseMirror-menubar-wrapper {
@apply flex flex-col;
.ProseMirror-menubar {
min-height: var(--space-two) !important;
@apply -ml-2.5 pb-0 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100;
@@ -717,6 +718,7 @@ export default {
@apply bg-slate-75 dark:bg-slate-800;
}
}
> .ProseMirror {
@apply p-0 break-words text-slate-800 dark:text-slate-100;
@@ -41,7 +41,7 @@
:show-query-operator="i !== appliedFilters.length - 1"
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
:grouped-filters="true"
:v="$v.appliedFilters.$each[i]"
:v="v$.appliedFilters.$each[i]"
@resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)"
/>
@@ -80,7 +80,7 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required, requiredIf } from 'vuelidate/lib/validators';
import { required, requiredIf } from '@vuelidate/validators';
import FilterInputBox from '../FilterInput/Index.vue';
import languages from './advancedFilterItems/languages';
import countries from 'shared/constants/countries.js';
@@ -89,6 +89,7 @@ import { filterAttributeGroups } from './advancedFilterItems';
import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -117,6 +118,9 @@ export default {
default: false,
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
appliedFilters: {
required,
@@ -347,8 +351,8 @@ export default {
}
},
submitFilterQuery() {
this.$v.$touch();
if (this.$v.$invalid) return;
this.v$.$touch();
if (this.v$.$invalid) return;
this.$store.dispatch(
'setConversationFilters',
JSON.parse(JSON.stringify(this.appliedFilters))
@@ -45,14 +45,14 @@
}}</label>
</div>
<div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1">
<label :class="{ error: $v.email.$error }">
<label :class="{ error: v$.email.$error }">
<input
v-model.trim="email"
type="text"
:placeholder="$t('EMAIL_TRANSCRIPT.FORM.EMAIL.PLACEHOLDER')"
@input="$v.email.$touch"
@input="v$.email.$touch"
/>
<span v-if="$v.email.$error" class="message">
<span v-if="v$.email.$error" class="message">
{{ $t('EMAIL_TRANSCRIPT.FORM.EMAIL.ERROR') }}
</span>
</label>
@@ -73,8 +73,10 @@
</template>
<script>
import { required, minLength, email } from 'vuelidate/lib/validators';
import { required, minLength, email } from '@vuelidate/validators';
import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
props: {
@@ -87,6 +89,9 @@ export default {
default: () => ({}),
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
email: '',
@@ -108,7 +113,7 @@ export default {
isFormValid() {
if (this.selectedType) {
if (this.sentToOtherEmailAddress) {
return !!this.email && !this.$v.email.$error;
return !!this.email && !this.v$.email.$error;
}
return true;
}
@@ -1,7 +1,10 @@
<template>
<li v-if="shouldRenderMessage" :id="`message${data.id}`" :class="alignBubble">
<div :class="wrapClass">
<div v-if="isFailed && !hasOneDayPassed" class="message-failed--alert">
<div
v-if="isFailed && !hasOneDayPassed && !isAnEmailInbox"
class="message-failed--alert"
>
<woot-button
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
size="tiny"
@@ -203,6 +206,10 @@ export default {
type: Boolean,
default: false,
},
isAnEmailInbox: {
type: Boolean,
default: false,
},
inboxSupportsReplyTo: {
type: Object,
default: () => ({}),
@@ -33,6 +33,7 @@
:is-a-whatsapp-channel="isAWhatsAppChannel"
:is-web-widget-inbox="isAWebWidgetInbox"
:is-a-facebook-inbox="isAFacebookInbox"
:is-an-email-inbox="isAnEmailChannel"
:is-instagram="isInstagramDM"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:in-reply-to="getInReplyToMessage(message)"
@@ -164,7 +164,7 @@ import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel
import ReplyEmailHead from './ReplyEmailHead.vue';
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
import ArticleSearchPopover from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue';
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert';
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert.vue';
import Banner from 'dashboard/components/ui/Banner.vue';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
@@ -198,7 +198,7 @@ import {
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { LocalStorage } from 'shared/helpers/localStorage';
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
export default {
components: {
@@ -1208,6 +1208,7 @@ export default {
@apply bg-yellow-50 dark:bg-yellow-200;
}
}
.send-button {
@apply mb-0;
}
@@ -1228,6 +1229,7 @@ export default {
.emoji-dialog--rtl {
@apply left-[unset] -right-80;
&::before {
transform: rotate(90deg);
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
@@ -1,15 +1,15 @@
<template>
<div>
<div v-if="toEmails">
<div class="input-group small" :class="{ error: $v.toEmailsVal.$error }">
<div class="input-group small" :class="{ error: v$.toEmailsVal.$error }">
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }}
</label>
<div class="input-group-field">
<woot-input
v-model.trim="$v.toEmailsVal.$model"
v-model.trim="v$.toEmailsVal.$model"
type="text"
:class="{ error: $v.toEmailsVal.$error }"
:class="{ error: v$.toEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur"
/>
@@ -17,15 +17,15 @@
</div>
</div>
<div class="input-group-wrap">
<div class="input-group small" :class="{ error: $v.ccEmailsVal.$error }">
<div class="input-group small" :class="{ error: v$.ccEmailsVal.$error }">
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }}
</label>
<div class="input-group-field">
<woot-input
v-model.trim="$v.ccEmailsVal.$model"
v-model.trim="v$.ccEmailsVal.$model"
type="text"
:class="{ error: $v.ccEmailsVal.$error }"
:class="{ error: v$.ccEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur"
/>
@@ -39,20 +39,20 @@
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.ADD_BCC') }}
</woot-button>
</div>
<span v-if="$v.ccEmailsVal.$error" class="message">
<span v-if="v$.ccEmailsVal.$error" class="message">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.ERROR') }}
</span>
</div>
<div v-if="showBcc" class="input-group-wrap">
<div class="input-group small" :class="{ error: $v.bccEmailsVal.$error }">
<div class="input-group small" :class="{ error: v$.bccEmailsVal.$error }">
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }}
</label>
<div class="input-group-field">
<woot-input
v-model.trim="$v.bccEmailsVal.$model"
v-model.trim="v$.bccEmailsVal.$model"
type="text"
:class="{ error: $v.bccEmailsVal.$error }"
:class="{ error: v$.bccEmailsVal.$error }"
:placeholder="
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')
"
@@ -60,7 +60,7 @@
/>
</div>
</div>
<span v-if="$v.bccEmailsVal.$error" class="message">
<span v-if="v$.bccEmailsVal.$error" class="message">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.ERROR') }}
</span>
</div>
@@ -137,7 +137,7 @@ export default {
this.showBcc = true;
},
onBlur() {
this.$v.$touch();
this.v$.$touch();
this.$emit('update:bccEmails', this.bccEmailsVal);
this.$emit('update:ccEmails', this.ccEmailsVal);
this.$emit('update:toEmails', this.toEmailsVal);
@@ -149,12 +149,14 @@ export default {
.input-group-wrap .message {
@apply text-sm text-red-500 dark:text-red-500;
}
.input-group {
@apply border-b border-solid border-slate-75 dark:border-slate-700 my-1;
.input-group-label {
@apply border-transparent bg-transparent text-xs font-semibold pl-0;
}
.input-group-field::v-deep input {
@apply mb-0 border-transparent;
}
@@ -162,6 +164,7 @@ export default {
.input-group.error {
@apply border-b-red-500 dark:border-b-red-500;
.input-group-label {
@apply text-red-500 dark:text-red-500;
}
@@ -45,7 +45,9 @@ const allKeysRequired = value => {
const keys = Object.keys(value);
return keys.every(key => value[key]);
};
import { requiredIf } from 'vuelidate/lib/validators';
import { useVuelidate } from '@vuelidate/core';
import { requiredIf } from '@vuelidate/validators';
export default {
props: {
template: {
@@ -53,6 +55,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
processedParams: {
requiredIfKeysPresent: requiredIf('variables'),
@@ -149,9 +154,11 @@ footer {
@apply ml-2.5;
}
}
.error {
@apply bg-red-100 dark:bg-red-100 rounded-md text-red-800 dark:text-red-800 p-2.5 text-center;
}
.template-input {
@apply bg-slate-25 dark:bg-slate-900 text-slate-700 dark:text-slate-100;
}
@@ -46,7 +46,7 @@ export default {
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/variables';
@import 'dashboard/assets/scss/variables';
.file {
display: flex;
@@ -1,4 +1,4 @@
import emailValidator from 'vuelidate/lib/validators/email';
import { email as emailValidator } from '@vuelidate/validators';
export const validEmailsByComma = value => {
if (!value.length) return true;
@@ -5,7 +5,7 @@
class="cursor-pointer py-2 pr-1.5 pl-2 rounded-tl-md rounded-bl-md flex items-center justify-center gap-1.5 bg-slate-25 dark:bg-slate-700 h-10 w-14"
@click="toggleCountryDropdown"
>
<h5 v-if="activeCountry.emoji" class="mb-0">
<h5 v-if="activeCountry" class="mb-0">
{{ activeCountry.emoji }}
</h5>
<fluent-icon v-else icon="globe" class="fluent-icon" size="16" />
@@ -6,12 +6,12 @@
<woot-input
v-model="value"
type="text"
:class="{ error: $v.value.$error }"
:class="{ error: v$.value.$error }"
:placeholder="confirmPlaceHolderText"
@blur="$v.value.$touch"
@blur="v$.value.$touch"
/>
<div class="button-wrapper">
<woot-button color-scheme="alert" :is-disabled="$v.value.$invalid">
<woot-button color-scheme="alert" :is-disabled="v$.value.$invalid">
{{ confirmText }}
</woot-button>
<woot-button class="clear" @click.prevent="closeModal">
@@ -23,14 +23,14 @@
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import Modal from '../../Modal.vue';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
Modal,
},
props: {
show: {
type: Boolean,
@@ -61,6 +61,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
value: '',
@@ -47,6 +47,8 @@ export const getCustomFields = ({ standardFields, customAttributes }) => {
type: attribute.attribute_display_type,
values: attribute.attribute_values,
field_type: attribute.attribute_model,
regex_pattern: attribute.regex_pattern,
regex_cue: attribute.regex_cue,
required: false,
enabled: false,
});
@@ -44,4 +44,18 @@ export default {
created_at: '2021-11-29T10:20:04.563Z',
},
],
customAttributesWithRegex: [
{
id: 2,
attribute_description: 'Test contact Attribute',
attribute_display_name: 'Test contact Attribute',
attribute_display_type: 'text',
attribute_key: 'test_contact_attribute',
attribute_model: 'contact_attribute',
attribute_values: Array(0),
created_at: '2023-09-20T10:20:04.563Z',
regex_pattern: '^w+$',
regex_cue: 'It should be a combination of alphabets and numbers',
},
],
};
@@ -5,7 +5,8 @@ import {
} from '../preChat';
import inboxFixture from './inboxFixture';
const { customFields, customAttributes } = inboxFixture;
const { customFields, customAttributes, customAttributesWithRegex } =
inboxFixture;
describe('#Pre chat Helpers', () => {
describe('getPreChatFields', () => {
it('should return correct pre-chat fields form options passed', () => {
@@ -27,7 +28,6 @@ describe('#Pre chat Helpers', () => {
placeholder: 'Please enter your email address',
type: 'email',
field_type: 'standard',
required: false,
enabled: false,
},
@@ -71,6 +71,26 @@ describe('#Pre chat Helpers', () => {
values: [],
},
]);
expect(
getCustomFields({
standardFields: { pre_chat_fields: customFields.pre_chat_fields },
customAttributes: customAttributesWithRegex,
})
).toEqual([
{
enabled: false,
label: 'Test contact Attribute',
placeholder: 'Test contact Attribute',
name: 'test_contact_attribute',
required: false,
field_type: 'contact_attribute',
type: 'text',
values: [],
regex_pattern: '^w+$',
regex_cue: 'It should be a combination of alphabets and numbers',
},
]);
});
});
});
@@ -39,6 +39,17 @@
"PLACEHOLDER": "Enter custom attribute key",
"ERROR": "Key is required",
"IN_VALID": "Invalid key"
},
"REGEX_PATTERN": {
"LABEL": "Regex Pattern",
"PLACEHOLDER": "Please enter custom attribute regex pattern. (Optional)"
},
"REGEX_CUE": {
"LABEL": "Regex Cue",
"PLACEHOLDER": "Please enter regex pattern hint. (Optional)"
},
"ENABLE_REGEX": {
"LABEL": "Enable regex validation"
}
},
"API": {
@@ -88,6 +99,17 @@
"EMPTY_RESULT": {
"404": "There are no custom attributes created",
"NOT_FOUND": "There are no custom attributes configured"
},
"REGEX_PATTERN": {
"LABEL": "Regex Pattern",
"PLACEHOLDER": "Please enter custom attribute regex pattern. (Optional)"
},
"REGEX_CUE": {
"LABEL": "Regex Cue",
"PLACEHOLDER": "Please enter regex pattern hint. (Optional)"
},
"ENABLE_REGEX": {
"LABEL": "Enable regex validation"
}
}
}
@@ -339,7 +339,8 @@
},
"VALIDATIONS": {
"REQUIRED": "Valid value is required",
"INVALID_URL": "Invalid URL"
"INVALID_URL": "Invalid URL",
"INVALID_INPUT": "Invalid Input"
}
},
"MERGE_CONTACTS": {
@@ -324,7 +324,8 @@
"LAST_EDITED": "Last edited"
},
"COLUMNS": {
"BY": "by"
"BY": "by",
"AUTHOR_NOT_AVAILABLE": "Author is not available"
}
},
"EDIT_ARTICLE": {
@@ -569,7 +569,7 @@
"UPDATE": "Update business hours settings",
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours visitors can be warned with a message and a pre-chat form.",
"DAY": {
"ENABLE": "Enable availability for this day",
"UNAVAILABLE": "Unavailable",
@@ -126,6 +126,6 @@
"NO_CONTENT": "Содержимое отсутствует",
"HIDE_QUOTED_TEXT": "Скрыть цитируемый текст",
"SHOW_QUOTED_TEXT": "Показать цитируемый текст",
"MESSAGE_READ": "Читать"
"MESSAGE_READ": "Прочитано"
}
}
@@ -150,8 +150,8 @@ export default {
}
},
submitAutomation() {
this.$v.$touch();
if (this.$v.$invalid) return;
this.v$.$touch();
if (this.v$.$invalid) return;
const automation = generateAutomationPayload(this.automation);
this.$emit('saveAutomation', automation, this.mode);
},
@@ -1,6 +1,10 @@
import { required, requiredIf } from 'vuelidate/lib/validators';
import { required, requiredIf } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
export default {
setup() {
return { v$: useVuelidate() };
},
validations: {
automation: {
name: {
@@ -0,0 +1,11 @@
export default {
methods: {
getRegexp(regexPatternValue) {
let lastSlash = regexPatternValue.lastIndexOf('/');
return new RegExp(
regexPatternValue.slice(1, lastSlash),
regexPatternValue.slice(lastSlash + 1)
);
},
},
};
@@ -8,12 +8,12 @@
<form class="w-full" @submit.prevent="addCustomAttribute">
<woot-input
v-model.trim="attributeName"
:class="{ error: $v.attributeName.$error }"
:class="{ error: v$.attributeName.$error }"
class="w-full"
:error="attributeNameError"
:label="$t('CUSTOM_ATTRIBUTES.FORM.NAME.LABEL')"
:placeholder="$t('CUSTOM_ATTRIBUTES.FORM.NAME.PLACEHOLDER')"
@input="$v.attributeName.$touch"
@input="v$.attributeName.$touch"
/>
<woot-input
v-model.trim="attributeValue"
@@ -23,7 +23,7 @@
/>
<div class="flex justify-end items-center py-2 px-0 gap-2">
<woot-button
:is-disabled="$v.attributeName.$invalid || isCreating"
:is-disabled="v$.attributeName.$invalid || isCreating"
:is-loading="isCreating"
>
{{ $t('CUSTOM_ATTRIBUTES.FORM.CREATE') }}
@@ -39,7 +39,8 @@
<script>
import Modal from 'dashboard/components/Modal.vue';
import alertMixin from 'shared/mixins/alertMixin';
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -56,6 +57,9 @@ export default {
default: false,
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
attributeValue: '',
@@ -70,7 +74,7 @@ export default {
},
computed: {
attributeNameError() {
if (this.$v.attributeName.$error) {
if (this.v$.attributeName.$error) {
return this.$t('CUSTOM_ATTRIBUTES.FORM.NAME.ERROR');
}
return '';
@@ -4,7 +4,7 @@
<div>
<div
class="mt-1 multiselect-wrap--medium"
:class="{ error: $v.parentContact.$error }"
:class="{ error: v$.parentContact.$error }"
>
<label class="multiselect__label">
{{ $t('MERGE_CONTACTS.PARENT.TITLE') }}
@@ -52,7 +52,7 @@
{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}
</span>
</multiselect>
<span v-if="$v.parentContact.$error" class="message">
<span v-if="v$.parentContact.$error" class="message">
{{ $t('MERGE_CONTACTS.FORM.CHILD_CONTACT.ERROR') }}
</span>
</div>
@@ -115,7 +115,8 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
import MergeContactSummary from 'dashboard/modules/contact/components/MergeContactSummary.vue';
import ContactDropdownItem from './ContactDropdownItem.vue';
@@ -141,6 +142,10 @@ export default {
default: () => [],
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
primaryContact: {
required,
@@ -165,8 +170,8 @@ export default {
this.$emit('search', query);
},
onSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
this.$emit('submit', this.parentContact.id);
@@ -45,7 +45,8 @@ export default {
</script>
<style scoped lang="scss">
@import '~dashboard/assets/scss/variables.scss';
@import 'dashboard/assets/scss/variables.scss';
.footer-wrap {
width: 100%;
display: flex;
@@ -41,7 +41,7 @@
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
:show-query-operator="i !== appliedFilters.length - 1"
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
:v="$v.appliedFilters.$each[i]"
:v="v$.appliedFilters.$each[i]"
@resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)"
/>
@@ -90,7 +90,7 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import FilterInputBox from '../../../../components/widgets/FilterInput/Index.vue';
import countries from 'shared/constants/countries.js';
import { mapGetters } from 'vuex';
@@ -98,6 +98,8 @@ import { filterAttributeGroups } from '../contactFilterItems';
import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
FilterInputBox,
@@ -125,6 +127,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
appliedFilters: {
required,
@@ -314,8 +319,8 @@ export default {
}
},
submitFilterQuery() {
this.$v.$touch();
if (this.$v.$invalid) return;
this.v$.$touch();
if (this.v$.$invalid) return;
this.$store.dispatch(
'contacts/setContactFilters',
JSON.parse(JSON.stringify(this.appliedFilters))
@@ -27,7 +27,7 @@
</section>
</template>
<script>
<script lang="jsx">
import { mixin as clickaway } from 'vue-clickaway';
import { VeTable } from 'vue-easytable';
import { getCountryFlag } from 'dashboard/helper/flag';
@@ -18,38 +18,38 @@
</div>
<div>
<div class="w-full">
<label :class="{ error: $v.name.$error }">
<label :class="{ error: v$.name.$error }">
{{ $t('CONTACT_FORM.FORM.NAME.LABEL') }}
<input
v-model.trim="name"
type="text"
:placeholder="$t('CONTACT_FORM.FORM.NAME.PLACEHOLDER')"
@input="$v.name.$touch"
@input="v$.name.$touch"
/>
</label>
<label :class="{ error: $v.email.$error }">
<label :class="{ error: v$.email.$error }">
{{ $t('CONTACT_FORM.FORM.EMAIL_ADDRESS.LABEL') }}
<input
v-model.trim="email"
type="text"
:placeholder="$t('CONTACT_FORM.FORM.EMAIL_ADDRESS.PLACEHOLDER')"
@input="$v.email.$touch"
@input="v$.email.$touch"
/>
<span v-if="$v.email.$error" class="message">
<span v-if="v$.email.$error" class="message">
{{ $t('CONTACT_FORM.FORM.EMAIL_ADDRESS.ERROR') }}
</span>
</label>
</div>
</div>
<div class="w-full">
<label :class="{ error: $v.description.$error }">
<label :class="{ error: v$.description.$error }">
{{ $t('CONTACT_FORM.FORM.BIO.LABEL') }}
<textarea
v-model.trim="description"
type="text"
:placeholder="$t('CONTACT_FORM.FORM.BIO.PLACEHOLDER')"
@input="$v.description.$touch"
@input="v$.description.$touch"
/>
</label>
</div>
@@ -67,7 +67,7 @@
:error="isPhoneNumberNotValid"
:placeholder="$t('CONTACT_FORM.FORM.PHONE_NUMBER.PLACEHOLDER')"
@input="onPhoneNumberInputChange"
@blur="$v.phoneNumber.$touch"
@blur="v$.phoneNumber.$touch"
@setCode="setPhoneCode"
/>
<span v-if="isPhoneNumberNotValid" class="message">
@@ -151,10 +151,11 @@ import {
DuplicateContactException,
ExceptionWithMessage,
} from 'shared/helpers/CustomErrors';
import { required, email } from 'vuelidate/lib/validators';
import { required, email } from '@vuelidate/validators';
import countries from 'shared/constants/countries.js';
import { isPhoneNumberValid } from 'shared/helpers/Validators';
import parsePhoneNumber from 'libphonenumber-js';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
@@ -172,6 +173,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
countries: countries,
@@ -364,8 +368,8 @@ export default {
}
},
async handleSubmit() {
this.$v.$touch();
if (this.$v.$invalid || this.isPhoneNumberNotValid) {
this.v$.$touch();
if (this.v$.$invalid || this.isPhoneNumberNotValid) {
return;
}
try {
@@ -13,7 +13,7 @@
</label>
<div
class="multiselect-wrap--small"
:class="{ 'has-multi-select-error': $v.targetInbox.$error }"
:class="{ 'has-multi-select-error': v$.targetInbox.$error }"
>
<multiselect
v-model="targetInbox"
@@ -47,8 +47,8 @@
</template>
</multiselect>
</div>
<label :class="{ error: $v.targetInbox.$error }">
<span v-if="$v.targetInbox.$error" class="message">
<label :class="{ error: v$.targetInbox.$error }">
<span v-if="v$.targetInbox.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }}
</span>
</label>
@@ -76,15 +76,15 @@
</div>
<div v-if="isAnEmailInbox" class="w-full">
<div class="w-full">
<label :class="{ error: $v.subject.$error }">
<label :class="{ error: v$.subject.$error }">
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.LABEL') }}
<input
v-model="subject"
type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.SUBJECT.PLACEHOLDER')"
@input="$v.subject.$touch"
@input="v$.subject.$touch"
/>
<span v-if="$v.subject.$error" class="message">
<span v-if="v$.subject.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.SUBJECT.ERROR') }}
</span>
</label>
@@ -112,13 +112,13 @@
<woot-message-editor
v-model="message"
class="message-editor"
:class="{ editor_warning: $v.message.$error }"
:class="{ editor_warning: v$.message.$error }"
:enable-variables="true"
:signature="signatureToApply"
:allow-signature="true"
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
@toggle-canned-menu="toggleCannedMenu"
@blur="$v.message.$touch"
@blur="v$.message.$touch"
>
<template #footer>
<message-signature-missing-alert
@@ -138,7 +138,7 @@
</div>
</template>
</woot-message-editor>
<span v-if="$v.message.$error" class="editor-warning__message">
<span v-if="v$.message.$error" class="editor-warning__message">
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
</span>
</div>
@@ -149,16 +149,16 @@
@on-select-template="toggleWaTemplate"
@on-send="onSendWhatsAppReply"
/>
<label v-else :class="{ error: $v.message.$error }">
<label v-else :class="{ error: v$.message.$error }">
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
<textarea
v-model="message"
class="min-h-[5rem]"
type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
@input="$v.message.$touch"
@input="v$.message.$touch"
/>
<span v-if="$v.message.$error" class="message">
<span v-if="v$.message.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
</span>
</label>
@@ -240,17 +240,17 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead.vue';
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.vue';
import MessageSignatureMissingAlert from 'dashboard/components/widgets/conversation/MessageSignatureMissingAlert';
import MessageSignatureMissingAlert from 'dashboard/components/widgets/conversation/MessageSignatureMissingAlert.vue';
import InboxDropdownItem from 'dashboard/components/widgets/InboxDropdownItem.vue';
import WhatsappTemplates from './WhatsappTemplates.vue';
import alertMixin from 'shared/mixins/alertMixin';
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
import { getInboxSource } from 'dashboard/helper/inbox';
import { required, requiredIf } from 'vuelidate/lib/validators';
import { required, requiredIf } from '@vuelidate/validators';
import inboxMixin from 'shared/mixins/inboxMixin';
import FileUpload from 'vue-upload-component';
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
import {
@@ -258,6 +258,7 @@ import {
removeSignature,
} from 'dashboard/helper/editorHelper';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -286,6 +287,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
name: '',
@@ -494,8 +498,8 @@ export default {
},
onFormSubmit() {
const isFromWhatsApp = false;
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
this.createConversation({
@@ -571,6 +575,7 @@ export default {
.file-uploads {
@apply text-start;
}
.multiselect-wrap--small.has-multi-select-error {
::v-deep {
.multiselect__tags {
@@ -583,9 +588,11 @@ export default {
.mention--box {
@apply left-0 m-auto right-0 top-auto h-fit;
}
.multiselect .multiselect__content .multiselect__option span {
@apply inline-flex w-6 text-slate-600 dark:text-slate-400;
}
.multiselect .multiselect__content .multiselect__option {
@apply py-0.5 px-1;
}
@@ -1,6 +1,6 @@
/* eslint arrow-body-style: 0 */
import { frontendURL } from '../../../helper/URLHelper';
const ConversationView = () => import('./ConversationView');
const ConversationView = () => import('./ConversationView.vue');
export default {
routes: [
@@ -11,6 +11,8 @@
emoji=""
:value="attribute.value"
:show-actions="true"
:attribute-regex="attribute.regex_pattern"
:regex-cue="attribute.regex_cue"
:class="attributeClass"
@update="onUpdate"
@delete="onDelete"
@@ -8,11 +8,11 @@
:label="$t('FILTER.CUSTOM_VIEWS.ADD.LABEL')"
type="text"
:error="
$v.name.$error ? $t('FILTER.CUSTOM_VIEWS.ADD.ERROR_MESSAGE') : ''
v$.name.$error ? $t('FILTER.CUSTOM_VIEWS.ADD.ERROR_MESSAGE') : ''
"
:class="{ error: $v.name.$error }"
:class="{ error: v$.name.$error }"
:placeholder="$t('FILTER.CUSTOM_VIEWS.ADD.PLACEHOLDER')"
@blur="$v.name.$touch"
@blur="v$.name.$touch"
/>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
@@ -29,9 +29,10 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import alertMixin from 'shared/mixins/alertMixin';
import { CONTACTS_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
@@ -49,7 +50,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
show: true,
@@ -59,7 +62,7 @@ export default {
computed: {
isButtonDisabled() {
return this.$v.name.$invalid;
return this.v$.name.$invalid;
},
},
@@ -75,8 +78,8 @@ export default {
this.$emit('close');
},
async saveCustomViews() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
try {
@@ -7,7 +7,7 @@
/>
<form class="w-full" @submit.prevent="onCreate">
<div class="w-full">
<label :class="{ error: $v.selectedLocale.$error }">
<label :class="{ error: v$.selectedLocale.$error }">
{{ $t('HELP_CENTER.PORTAL.ADD_LOCALE.LOCALE.LABEL') }}
<select v-model="selectedLocale">
<option
@@ -18,7 +18,7 @@
{{ locale.name }}-{{ locale.code }}
</option>
</select>
<span v-if="$v.selectedLocale.$error" class="message">
<span v-if="v$.selectedLocale.$error" class="message">
{{ $t('HELP_CENTER.PORTAL.ADD_LOCALE.LOCALE.ERROR') }}
</span>
</label>
@@ -41,9 +41,11 @@
<script>
import Modal from 'dashboard/components/Modal.vue';
import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import allLocales from 'shared/constants/locales.js';
import { PORTALS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
Modal,
@@ -59,6 +61,9 @@ export default {
default: () => ({}),
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
selectedLocale: '',
@@ -94,8 +99,8 @@ export default {
},
methods: {
async onCreate() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
const updatedLocales = this.addedLocales;
@@ -137,6 +142,7 @@ export default {
input {
margin-bottom: 0;
}
.message {
margin-top: 0;
}
@@ -11,9 +11,30 @@
{{ title }}
</h6>
</router-link>
<div class="author">
<span class="by">{{ $t('HELP_CENTER.TABLE.COLUMNS.BY') }}</span>
<span class="name">{{ articleAuthorName }}</span>
<div class="flex gap-1 items-center">
<Thumbnail
v-if="author"
:src="author.thumbnail"
:username="author.name"
size="16px"
/>
<div
v-else
v-tooltip.right="
$t('HELP_CENTER.TABLE.COLUMNS.AUTHOR_NOT_AVAILABLE')
"
class="flex items-center justify-center rounded w-4 h-4 bg-woot-100 dark:bg-woot-700"
>
<fluent-icon
icon="person"
type="filled"
size="10"
class="text-woot-300 dark:text-woot-300"
/>
</div>
<span class="font-normal text-slate-500 dark:text-slate-200 text-sm">
{{ articleAuthorName }}
</span>
</div>
</div>
</span>
@@ -57,10 +78,12 @@ import timeMixin from 'dashboard/mixins/time';
import portalMixin from '../mixins/portalMixin';
import { frontendURL } from 'dashboard/helper/URLHelper';
import EmojiOrIcon from '../../../../../shared/components/EmojiOrIcon.vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
export default {
components: {
EmojiOrIcon,
Thumbnail,
},
mixins: [timeMixin, portalMixin],
props: {
@@ -110,7 +133,7 @@ export default {
}).format(this.views || 0);
},
articleAuthorName() {
return this.author.name;
return this.author?.name || '-';
},
labelColor() {
switch (this.status) {
@@ -189,15 +212,6 @@ export default {
.article-block {
@apply min-w-0;
}
.author {
.by {
@apply font-normal text-slate-500 dark:text-slate-200 text-sm;
}
.name {
@apply font-normal text-slate-500 dark:text-slate-200 text-sm;
}
}
}
span {
@@ -1,7 +1,7 @@
<template>
<div class="w-full">
<div
class="my-0 py-0 px-4 grid grid-cols-6 md:grid-cols-7 lg:grid-cols-8 gap-4 border-b border-slate-100 dark:border-slate-700 sticky top-16 bg-white dark:bg-slate-900"
class="my-0 py-0 px-4 grid grid-cols-6 z-10 md:grid-cols-7 lg:grid-cols-8 gap-4 border-b border-slate-100 dark:border-slate-700 sticky top-16 bg-white dark:bg-slate-900"
:class="{ draggable: onCategoryPage }"
>
<div
@@ -59,7 +59,7 @@
</template>
<script>
import { mapGetters } from 'vuex';
import UpgradePage from './UpgradePage';
import UpgradePage from './UpgradePage.vue';
import { frontendURL } from '../../../../helper/URLHelper';
import Sidebar from 'dashboard/components/layout/Sidebar.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents';
@@ -42,35 +42,35 @@
<div class="mb-4">
<woot-input
v-model.trim="name"
:class="{ error: $v.name.$error }"
:class="{ error: v$.name.$error }"
:error="nameError"
:label="$t('HELP_CENTER.PORTAL.ADD.NAME.LABEL')"
:placeholder="$t('HELP_CENTER.PORTAL.ADD.NAME.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.PORTAL.ADD.NAME.HELP_TEXT')"
@blur="$v.name.$touch"
@blur="v$.name.$touch"
@input="onNameChange"
/>
</div>
<div class="mb-4">
<woot-input
v-model.trim="slug"
:class="{ error: $v.slug.$error }"
:class="{ error: v$.slug.$error }"
:error="slugError"
:label="$t('HELP_CENTER.PORTAL.ADD.SLUG.LABEL')"
:placeholder="$t('HELP_CENTER.PORTAL.ADD.SLUG.PLACEHOLDER')"
:help-text="domainHelpText"
@blur="$v.slug.$touch"
@blur="v$.slug.$touch"
/>
</div>
<div class="mb-4">
<woot-input
v-model.trim="domain"
:class="{ error: $v.domain.$error }"
:class="{ error: v$.domain.$error }"
:label="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.LABEL')"
:placeholder="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.PLACEHOLDER')"
:help-text="domainExampleHelpText"
:error="domainError"
@blur="$v.domain.$touch"
@blur="v$.domain.$touch"
/>
</div>
</div>
@@ -78,7 +78,7 @@
<div class="flex-end">
<woot-button
:is-loading="isSubmitting"
:is-disabled="$v.$invalid"
:is-disabled="v$.$invalid"
@click="onSubmitClick"
>
{{ submitButtonText }}
@@ -88,8 +88,9 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { isDomain } from 'shared/helpers/Validators';
import { useVuelidate } from '@vuelidate/core';
import alertMixin from 'shared/mixins/alertMixin';
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
@@ -118,6 +119,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
name: '',
@@ -144,19 +148,19 @@ export default {
},
computed: {
nameError() {
if (this.$v.name.$error) {
if (this.v$.name.$error) {
return this.$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR');
}
return '';
},
slugError() {
if (this.$v.slug.$error) {
if (this.v$.slug.$error) {
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
}
return '';
},
domainError() {
if (this.$v.domain.$error) {
if (this.v$.domain.$error) {
return this.$t('HELP_CENTER.PORTAL.ADD.DOMAIN.ERROR');
}
return '';
@@ -192,8 +196,8 @@ export default {
this.slug = convertToCategorySlug(this.name);
},
onSubmitClick() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
@@ -251,6 +255,7 @@ export default {
input {
@apply mb-1;
}
.help-text {
@apply mb-0;
}
@@ -47,12 +47,12 @@
"
:help-text="homepageExampleHelpText"
:error="
$v.homePageLink.$error
v$.homePageLink.$error
? $t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.ERROR')
: ''
"
:class="{ error: $v.homePageLink.$error }"
@blur="$v.homePageLink.$touch"
:class="{ error: v$.homePageLink.$error }"
@blur="v$.homePageLink.$touch"
/>
</div>
</div>
@@ -60,7 +60,7 @@
<div class="flex-end">
<woot-button
:is-loading="isSubmitting"
:is-disabled="$v.$invalid"
:is-disabled="v$.$invalid"
@click="onSubmitClick"
>
{{
@@ -74,8 +74,9 @@
</template>
<script>
import { url } from 'vuelidate/lib/validators';
import { url } from '@vuelidate/validators';
import { getRandomColor } from 'dashboard/helper/labelColor';
import { useVuelidate } from '@vuelidate/core';
import alertMixin from 'shared/mixins/alertMixin';
import wootConstants from 'dashboard/constants/globals';
@@ -83,7 +84,6 @@ import wootConstants from 'dashboard/constants/globals';
const { EXAMPLE_URL } = wootConstants;
export default {
components: {},
mixins: [alertMixin],
props: {
portal: {
@@ -95,6 +95,9 @@ export default {
default: false,
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
color: '#000',
@@ -132,8 +135,8 @@ export default {
}
},
onSubmitClick() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
const portal = {
@@ -158,9 +161,11 @@ export default {
input {
@apply mb-1;
}
.help-text {
@apply mb-0;
}
.colorpicker--selected {
@apply mb-0;
}
@@ -1,30 +1,32 @@
import HelpCenterLayout from './components/HelpCenterLayout';
import HelpCenterLayout from './components/HelpCenterLayout.vue';
import { getPortalRoute } from './helpers/routeHelper';
const ListAllPortals = () => import('./pages/portals/ListAllPortals');
const NewPortal = () => import('./pages/portals/NewPortal');
const ListAllPortals = () => import('./pages/portals/ListAllPortals.vue');
const NewPortal = () => import('./pages/portals/NewPortal.vue');
const EditPortal = () => import('./pages/portals/EditPortal');
const EditPortalBasic = () => import('./pages/portals/EditPortalBasic');
const EditPortal = () => import('./pages/portals/EditPortal.vue');
const EditPortalBasic = () => import('./pages/portals/EditPortalBasic.vue');
const EditPortalCustomization = () =>
import('./pages/portals/EditPortalCustomization');
import('./pages/portals/EditPortalCustomization.vue');
const EditPortalLocales = () => import('./pages/portals/EditPortalLocales.vue');
const ShowPortal = () => import('./pages/portals/ShowPortal');
const PortalDetails = () => import('./pages/portals/PortalDetails');
const PortalCustomization = () => import('./pages/portals/PortalCustomization');
const ShowPortal = () => import('./pages/portals/ShowPortal.vue');
const PortalDetails = () => import('./pages/portals/PortalDetails.vue');
const PortalCustomization = () =>
import('./pages/portals/PortalCustomization.vue');
const PortalSettingsFinish = () =>
import('./pages/portals/PortalSettingsFinish');
import('./pages/portals/PortalSettingsFinish.vue');
const ListAllCategories = () => import('./pages/categories/ListAllCategories');
const NewCategory = () => import('./pages/categories/NewCategory');
const EditCategory = () => import('./pages/categories/EditCategory');
const ListAllCategories = () =>
import('./pages/categories/ListAllCategories.vue');
const NewCategory = () => import('./pages/categories/NewCategory.vue');
const EditCategory = () => import('./pages/categories/EditCategory.vue');
const ListCategoryArticles = () =>
import('./pages/articles/ListCategoryArticles');
const ListAllArticles = () => import('./pages/articles/ListAllArticles');
import('./pages/articles/ListCategoryArticles.vue');
const ListAllArticles = () => import('./pages/articles/ListAllArticles.vue');
const DefaultPortalArticles = () =>
import('./pages/articles/DefaultPortalArticles');
const NewArticle = () => import('./pages/articles/NewArticle');
const EditArticle = () => import('./pages/articles/EditArticle');
import('./pages/articles/DefaultPortalArticles.vue');
const NewArticle = () => import('./pages/articles/NewArticle.vue');
const EditArticle = () => import('./pages/articles/EditArticle.vue');
const portalRoutes = [
{
@@ -25,20 +25,20 @@
:label="$t('HELP_CENTER.CATEGORY.ADD.NAME.LABEL')"
:placeholder="$t('HELP_CENTER.CATEGORY.ADD.NAME.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.CATEGORY.ADD.NAME.HELP_TEXT')"
:has-error="$v.name.$error"
:has-error="v$.name.$error"
:error-message="$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR')"
@name-change="onNameChange"
@icon-change="onClickInsertEmoji"
/>
<woot-input
v-model.trim="slug"
:class="{ error: $v.slug.$error }"
:class="{ error: v$.slug.$error }"
class="w-full"
:error="slugError"
:label="$t('HELP_CENTER.CATEGORY.ADD.SLUG.LABEL')"
:placeholder="$t('HELP_CENTER.CATEGORY.ADD.SLUG.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.CATEGORY.ADD.SLUG.HELP_TEXT')"
@input="$v.slug.$touch"
@input="v$.slug.$touch"
/>
<label>
{{ $t('HELP_CENTER.CATEGORY.ADD.DESCRIPTION.LABEL') }}
@@ -68,10 +68,11 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
import CategoryNameIconInput from './NameEmojiInput.vue';
import { useVuelidate } from '@vuelidate/core';
export default {
components: { CategoryNameIconInput },
@@ -94,6 +95,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
name: '',
@@ -118,7 +122,7 @@ export default {
: this.portalSlug;
},
slugError() {
if (this.$v.slug.$error) {
if (this.v$.slug.$error) {
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
}
return '';
@@ -148,8 +152,8 @@ export default {
description,
locale,
};
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
try {
@@ -25,7 +25,7 @@
:label="$t('HELP_CENTER.CATEGORY.EDIT.NAME.LABEL')"
:placeholder="$t('HELP_CENTER.CATEGORY.EDIT.NAME.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.CATEGORY.EDIT.NAME.HELP_TEXT')"
:has-error="$v.name.$error"
:has-error="v$.name.$error"
:error-message="$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR')"
:existing-name="category.name"
:saved-icon="category.icon"
@@ -34,13 +34,13 @@
/>
<woot-input
v-model.trim="slug"
:class="{ error: $v.slug.$error }"
:class="{ error: v$.slug.$error }"
class="w-full"
:error="slugError"
:label="$t('HELP_CENTER.CATEGORY.EDIT.SLUG.LABEL')"
:placeholder="$t('HELP_CENTER.CATEGORY.EDIT.SLUG.PLACEHOLDER')"
:help-text="$t('HELP_CENTER.CATEGORY.EDIT.SLUG.HELP_TEXT')"
@input="$v.slug.$touch"
@input="v$.slug.$touch"
/>
<label>
{{ $t('HELP_CENTER.CATEGORY.EDIT.DESCRIPTION.LABEL') }}
@@ -70,10 +70,11 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
import CategoryNameIconInput from './NameEmojiInput.vue';
import { useVuelidate } from '@vuelidate/core';
export default {
components: { CategoryNameIconInput },
@@ -100,6 +101,9 @@ export default {
default: '',
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
id: this.category.id,
@@ -120,7 +124,7 @@ export default {
},
computed: {
slugError() {
if (this.$v.slug.$error) {
if (this.v$.slug.$error) {
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
}
return '';
@@ -159,8 +163,8 @@ export default {
slug,
description,
};
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
try {
@@ -190,6 +194,7 @@ export default {
.article-info {
width: 100%;
margin: 0 0 var(--space-normal);
.value {
color: var(--s-600);
}
@@ -38,7 +38,7 @@
<script>
import { mixin as clickaway } from 'vue-clickaway';
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
export default {
components: { EmojiInput },
@@ -56,7 +56,7 @@
<span
class="mt-1 text-slate-500 dark:text-slate-400 text-xxs font-semibold flex"
>
{{ dynamicTime(notificationItem.created_at) }}
{{ dynamicTime(notificationItem.last_activity_at) }}
</span>
</div>
</div>
@@ -58,7 +58,7 @@
<td>
<div class="text-right timestamp--column">
<span class="notification--created-at">
{{ dynamicTime(notificationItem.created_at) }}
{{ dynamicTime(notificationItem.last_activity_at) }}
</span>
</div>
</td>
@@ -130,7 +130,7 @@ export default {
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/mixins';
@import 'dashboard/assets/scss/mixins';
.notification--title {
@apply text-sm m-0 text-slate-800 dark:text-slate-100;
@@ -11,6 +11,7 @@
<table-footer
:current-page="Number(meta.currentPage)"
:total-count="meta.count"
:page-size="15"
@page-change="onPageChange"
/>
</div>
@@ -13,19 +13,19 @@
<p>{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.NOTE') }}</p>
</div>
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
<label :class="{ error: $v.name.$error }">
<label :class="{ error: v$.name.$error }">
{{ $t('GENERAL_SETTINGS.FORM.NAME.LABEL') }}
<input
v-model="name"
type="text"
:placeholder="$t('GENERAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
@blur="$v.name.$touch"
@blur="v$.name.$touch"
/>
<span v-if="$v.name.$error" class="message">
<span v-if="v$.name.$error" class="message">
{{ $t('GENERAL_SETTINGS.FORM.NAME.ERROR') }}
</span>
</label>
<label :class="{ error: $v.locale.$error }">
<label :class="{ error: v$.locale.$error }">
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.LABEL') }}
<select v-model="locale">
<option
@@ -36,7 +36,7 @@
{{ lang.name }}
</option>
</select>
<span v-if="$v.locale.$error" class="message">
<span v-if="v$.locale.$error" class="message">
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.ERROR') }}
</span>
</label>
@@ -68,7 +68,7 @@
</label>
<label
v-if="showAutoResolutionConfig"
:class="{ error: $v.autoResolveDuration.$error }"
:class="{ error: v$.autoResolveDuration.$error }"
>
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.LABEL') }}
<input
@@ -77,9 +77,9 @@
:placeholder="
$t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.PLACEHOLDER')
"
@blur="$v.autoResolveDuration.$touch"
@blur="v$.autoResolveDuration.$touch"
/>
<span v-if="$v.autoResolveDuration.$error" class="message">
<span v-if="v$.autoResolveDuration.$error" class="message">
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.ERROR') }}
</span>
</label>
@@ -129,7 +129,7 @@
</template>
<script>
import { required, minValue, maxValue } from 'vuelidate/lib/validators';
import { required, minValue, maxValue } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import configMixin from 'shared/mixins/configMixin';
@@ -138,9 +138,13 @@ import { FEATURE_FLAGS } from '../../../../featureFlags';
import semver from 'semver';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [accountMixin, alertMixin, configMixin, uiSettingsMixin],
setup() {
return { v$: useVuelidate() };
},
data() {
return {
id: '',
@@ -251,8 +255,8 @@ export default {
},
async updateAccount() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
this.showAlert(this.$t('GENERAL_SETTINGS.FORM.ERROR'));
return;
}
@@ -5,7 +5,7 @@
<div class="h-[calc(100vh-56px)] relative">
<csml-monaco-editor v-model="bot.csmlContent" class="w-full h-full" />
<div
v-if="$v.bot.csmlContent.$error"
v-if="v$.bot.csmlContent.$error"
class="bg-red-100 dark:bg-red-200 text-white dark:text-white absolute bottom-0 w-full p-2.5 flex items-center text-xs justify-center flex-shrink-0"
>
<span>{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.BOT_CONFIG.ERROR') }}</span>
@@ -18,14 +18,14 @@
@submit.prevent="onSubmit"
>
<div>
<label :class="{ error: $v.bot.name.$error }">
<label :class="{ error: v$.bot.name.$error }">
{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.NAME.LABEL') }}
<input
v-model="bot.name"
type="text"
:placeholder="$t('AGENT_BOTS.CSML_BOT_EDITOR.NAME.PLACEHOLDER')"
/>
<span v-if="$v.bot.name.$error" class="message">
<span v-if="v$.bot.name.$error" class="message">
{{ $t('AGENT_BOTS.CSML_BOT_EDITOR.NAME.ERROR') }}
</span>
</label>
@@ -50,8 +50,9 @@
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import CsmlMonacoEditor from './CSMLMonacoEditor.vue';
import { useVuelidate } from '@vuelidate/core';
export default {
components: { CsmlMonacoEditor },
@@ -61,6 +62,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
validations: {
bot: {
name: { required },
@@ -78,8 +82,8 @@ export default {
},
methods: {
onSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
this.$emit('submit', {
@@ -11,37 +11,37 @@
@submit.prevent="addAgent()"
>
<div class="w-full">
<label :class="{ error: $v.agentName.$error }">
<label :class="{ error: v$.agentName.$error }">
{{ $t('AGENT_MGMT.ADD.FORM.NAME.LABEL') }}
<input
v-model.trim="agentName"
type="text"
:placeholder="$t('AGENT_MGMT.ADD.FORM.NAME.PLACEHOLDER')"
@input="$v.agentName.$touch"
@input="v$.agentName.$touch"
/>
</label>
</div>
<div class="w-full">
<label :class="{ error: $v.agentType.$error }">
<label :class="{ error: v$.agentType.$error }">
{{ $t('AGENT_MGMT.ADD.FORM.AGENT_TYPE.LABEL') }}
<select v-model="agentType">
<option v-for="role in roles" :key="role.name" :value="role.name">
{{ role.label }}
</option>
</select>
<span v-if="$v.agentType.$error" class="message">
<span v-if="v$.agentType.$error" class="message">
{{ $t('AGENT_MGMT.ADD.FORM.AGENT_TYPE.ERROR') }}
</span>
</label>
</div>
<div class="w-full">
<label :class="{ error: $v.agentEmail.$error }">
<label :class="{ error: v$.agentEmail.$error }">
{{ $t('AGENT_MGMT.ADD.FORM.EMAIL.LABEL') }}
<input
v-model.trim="agentEmail"
type="text"
:placeholder="$t('AGENT_MGMT.ADD.FORM.EMAIL.PLACEHOLDER')"
@input="$v.agentEmail.$touch"
@input="v$.agentEmail.$touch"
/>
</label>
</div>
@@ -49,8 +49,8 @@
<div class="w-full">
<woot-submit-button
:disabled="
$v.agentEmail.$invalid ||
$v.agentName.$invalid ||
v$.agentEmail.$invalid ||
v$.agentName.$invalid ||
uiFlags.isCreating
"
:button-text="$t('AGENT_MGMT.ADD.FORM.SUBMIT')"
@@ -67,8 +67,9 @@
</template>
<script>
import { required, minLength, email } from 'vuelidate/lib/validators';
import { required, minLength, email } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import { useVuelidate } from '@vuelidate/core';
export default {
props: {
@@ -77,6 +78,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
agentName: '',
@@ -4,33 +4,33 @@
<woot-modal-header :header-title="pageTitle" />
<form class="w-full" @submit.prevent="editAgent()">
<div class="w-full">
<label :class="{ error: $v.agentName.$error }">
<label :class="{ error: v$.agentName.$error }">
{{ $t('AGENT_MGMT.EDIT.FORM.NAME.LABEL') }}
<input
v-model.trim="agentName"
type="text"
:placeholder="$t('AGENT_MGMT.EDIT.FORM.NAME.PLACEHOLDER')"
@input="$v.agentName.$touch"
@input="v$.agentName.$touch"
/>
</label>
</div>
<div class="w-full">
<label :class="{ error: $v.agentType.$error }">
<label :class="{ error: v$.agentType.$error }">
{{ $t('AGENT_MGMT.EDIT.FORM.AGENT_TYPE.LABEL') }}
<select v-model="agentType">
<option v-for="role in roles" :key="role.name" :value="role.name">
{{ role.label }}
</option>
</select>
<span v-if="$v.agentType.$error" class="message">
<span v-if="v$.agentType.$error" class="message">
{{ $t('AGENT_MGMT.EDIT.FORM.AGENT_TYPE.ERROR') }}
</span>
</label>
</div>
<div class="w-full">
<label :class="{ error: $v.agentAvailability.$error }">
<label :class="{ error: v$.agentAvailability.$error }">
{{ $t('PROFILE_SETTINGS.FORM.AVAILABILITY.LABEL') }}
<select v-model="agentAvailability">
<option
@@ -41,7 +41,7 @@
{{ role.label }}
</option>
</select>
<span v-if="$v.agentAvailability.$error" class="message">
<span v-if="v$.agentAvailability.$error" class="message">
{{ $t('AGENT_MGMT.EDIT.FORM.AGENT_AVAILABILITY.ERROR') }}
</span>
</label>
@@ -50,8 +50,8 @@
<div class="w-[50%]">
<woot-submit-button
:disabled="
$v.agentType.$invalid ||
$v.agentName.$invalid ||
v$.agentType.$invalid ||
v$.agentName.$invalid ||
uiFlags.isUpdating
"
:button-text="$t('AGENT_MGMT.EDIT.FORM.SUBMIT')"
@@ -77,12 +77,13 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import WootSubmitButton from '../../../../components/buttons/FormSubmitButton.vue';
import Modal from '../../../../components/Modal.vue';
import Auth from '../../../../api/auth';
import wootConstants from 'dashboard/constants/globals';
import { useVuelidate } from '@vuelidate/core';
const { AVAILABILITY_STATUS_KEYS } = wootConstants;
@@ -117,6 +118,9 @@ export default {
required: true,
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
roles: [
@@ -5,14 +5,14 @@
<form class="flex w-full" @submit.prevent="addAttributes">
<div class="w-full">
<label :class="{ error: $v.attributeModel.$error }">
<label :class="{ error: v$.attributeModel.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.MODEL.LABEL') }}
<select v-model="attributeModel">
<option v-for="model in models" :key="model.id" :value="model.id">
{{ model.option }}
</option>
</select>
<span v-if="$v.attributeModel.$error" class="message">
<span v-if="v$.attributeModel.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.MODEL.ERROR') }}
</span>
</label>
@@ -20,46 +20,46 @@
v-model="displayName"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.LABEL')"
type="text"
:class="{ error: $v.displayName.$error }"
:class="{ error: v$.displayName.$error }"
:error="
$v.displayName.$error
v$.displayName.$error
? $t('ATTRIBUTES_MGMT.ADD.FORM.NAME.ERROR')
: ''
"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.PLACEHOLDER')"
@input="onDisplayNameChange"
@blur="$v.displayName.$touch"
@blur="v$.displayName.$touch"
/>
<woot-input
v-model="attributeKey"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL')"
type="text"
:class="{ error: $v.attributeKey.$error }"
:error="$v.attributeKey.$error ? keyErrorMessage : ''"
:class="{ error: v$.attributeKey.$error }"
:error="v$.attributeKey.$error ? keyErrorMessage : ''"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.PLACEHOLDER')"
@blur="$v.attributeKey.$touch"
@blur="v$.attributeKey.$touch"
/>
<label :class="{ error: $v.description.$error }">
<label :class="{ error: v$.description.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.LABEL') }}
<textarea
v-model="description"
rows="3"
type="text"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.DESC.PLACEHOLDER')"
@blur="$v.description.$touch"
@blur="v$.description.$touch"
/>
<span v-if="$v.description.$error" class="message">
<span v-if="v$.description.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.ERROR') }}
</span>
</label>
<label :class="{ error: $v.attributeType.$error }">
<label :class="{ error: v$.attributeType.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.LABEL') }}
<select v-model="attributeType">
<option v-for="type in types" :key="type.id" :value="type.id">
{{ type.option }}
</option>
</select>
<span v-if="$v.attributeType.$error" class="message">
<span v-if="v$.attributeType.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.ERROR') }}
</span>
</label>
@@ -86,6 +86,30 @@
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.LIST.ERROR') }}
</label>
</div>
<div v-if="isAttributeTypeText">
<input
v-model="regexEnabled"
type="checkbox"
@input="toggleRegexEnabled"
/>
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.ENABLE_REGEX.LABEL') }}
</div>
<woot-input
v-if="isAttributeTypeText && isRegexEnabled"
v-model="regexPattern"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_PATTERN.LABEL')"
type="text"
:placeholder="
$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_PATTERN.PLACEHOLDER')
"
/>
<woot-input
v-if="isAttributeTypeText && isRegexEnabled"
v-model="regexCue"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_CUE.LABEL')"
type="text"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_CUE.PLACEHOLDER')"
/>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-submit-button
:disabled="isButtonDisabled"
@@ -102,11 +126,12 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { mapGetters } from 'vuex';
import { convertToAttributeSlug } from 'dashboard/helper/commons.js';
import { ATTRIBUTE_MODELS, ATTRIBUTE_TYPES } from './constants';
import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
mixins: [alertMixin],
@@ -116,6 +141,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
@@ -124,6 +152,9 @@ export default {
attributeModel: 0,
attributeType: 0,
attributeKey: '',
regexPattern: null,
regexCue: null,
regexEnabled: false,
models: ATTRIBUTE_MODELS,
types: ATTRIBUTE_TYPES,
values: [],
@@ -148,14 +179,14 @@ export default {
},
isButtonDisabled() {
return (
this.$v.displayName.$invalid ||
this.$v.description.$invalid ||
this.v$.displayName.$invalid ||
this.v$.description.$invalid ||
this.uiFlags.isCreating ||
this.isTagInputInvalid
);
},
keyErrorMessage() {
if (!this.$v.attributeKey.isKey) {
if (!this.v$.attributeKey.isKey) {
return this.$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.IN_VALID');
}
return this.$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.ERROR');
@@ -163,6 +194,12 @@ export default {
isAttributeTypeList() {
return this.attributeType === 6;
},
isAttributeTypeText() {
return this.attributeType === 0;
},
isRegexEnabled() {
return this.regexEnabled;
},
},
validations: {
@@ -201,11 +238,18 @@ export default {
onDisplayNameChange() {
this.attributeKey = convertToAttributeSlug(this.displayName);
},
toggleRegexEnabled() {
this.regexEnabled = !this.regexEnabled;
},
async addAttributes() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
if (!this.regexEnabled) {
this.regexPattern = null;
this.regexCue = null;
}
try {
await this.$store.dispatch('attributes/create', {
attribute_display_name: this.displayName,
@@ -214,6 +258,10 @@ export default {
attribute_display_type: this.attributeType,
attribute_key: this.attributeKey,
attribute_values: this.attributeListValues,
regex_pattern: this.regexPattern
? new RegExp(this.regexPattern).toString()
: null,
regex_cue: this.regexCue,
});
this.alertMessage = this.$t('ATTRIBUTES_MGMT.ADD.API.SUCCESS_MESSAGE');
this.onClose();
@@ -233,13 +281,16 @@ export default {
padding: 0 var(--space-small) var(--space-small) 0;
font-family: monospace;
}
.multiselect--wrap {
margin-bottom: var(--space-normal);
.error-message {
color: var(--r-400);
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
}
.invalid {
::v-deep {
.multiselect__tags {
@@ -248,13 +299,16 @@ export default {
}
}
}
::v-deep {
.multiselect {
margin-bottom: 0;
}
.multiselect__content-wrapper {
display: none;
}
.multiselect--active .multiselect__tags {
border-radius: var(--border-radius-normal);
}
@@ -7,46 +7,46 @@
v-model.trim="displayName"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.LABEL')"
type="text"
:class="{ error: $v.displayName.$error }"
:class="{ error: v$.displayName.$error }"
:error="
$v.displayName.$error
v$.displayName.$error
? $t('ATTRIBUTES_MGMT.ADD.FORM.NAME.ERROR')
: ''
"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.NAME.PLACEHOLDER')"
@blur="$v.displayName.$touch"
@blur="v$.displayName.$touch"
/>
<woot-input
v-model.trim="attributeKey"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.LABEL')"
type="text"
:class="{ error: $v.attributeKey.$error }"
:error="$v.attributeKey.$error ? keyErrorMessage : ''"
:class="{ error: v$.attributeKey.$error }"
:error="v$.attributeKey.$error ? keyErrorMessage : ''"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.PLACEHOLDER')"
readonly
@blur="$v.attributeKey.$touch"
@blur="v$.attributeKey.$touch"
/>
<label :class="{ error: $v.description.$error }">
<label :class="{ error: v$.description.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.LABEL') }}
<textarea
v-model.trim="description"
rows="5"
type="text"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.DESC.PLACEHOLDER')"
@blur="$v.description.$touch"
@blur="v$.description.$touch"
/>
<span v-if="$v.description.$error" class="message">
<span v-if="v$.description.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.DESC.ERROR') }}
</span>
</label>
<label :class="{ error: $v.attributeType.$error }">
<label :class="{ error: v$.attributeType.$error }">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.LABEL') }}
<select v-model="attributeType" disabled>
<option v-for="type in types" :key="type.id" :value="type.id">
{{ type.option }}
</option>
</select>
<span v-if="$v.attributeType.$error" class="message">
<span v-if="v$.attributeType.$error" class="message">
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.ERROR') }}
</span>
</label>
@@ -70,6 +70,30 @@
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.TYPE.LIST.ERROR') }}
</label>
</div>
<div v-if="isAttributeTypeText">
<input
v-model="regexEnabled"
type="checkbox"
@input="toggleRegexEnabled"
/>
{{ $t('ATTRIBUTES_MGMT.ADD.FORM.ENABLE_REGEX.LABEL') }}
</div>
<woot-input
v-if="isAttributeTypeText && isRegexEnabled"
v-model="regexPattern"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_PATTERN.LABEL')"
type="text"
:placeholder="
$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_PATTERN.PLACEHOLDER')
"
/>
<woot-input
v-if="isAttributeTypeText && isRegexEnabled"
v-model="regexCue"
:label="$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_CUE.LABEL')"
type="text"
:placeholder="$t('ATTRIBUTES_MGMT.ADD.FORM.REGEX_CUE.PLACEHOLDER')"
/>
</div>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-button :is-loading="isUpdating" :disabled="isButtonDisabled">
@@ -85,12 +109,14 @@
<script>
import { mapGetters } from 'vuex';
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { ATTRIBUTE_TYPES } from './constants';
import alertMixin from 'shared/mixins/alertMixin';
import customAttributeMixin from '../../../../mixins/customAttributeMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {},
mixins: [alertMixin],
mixins: [alertMixin, customAttributeMixin],
props: {
selectedAttribute: {
type: Object,
@@ -101,11 +127,17 @@ export default {
default: false,
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
displayName: '',
description: '',
attributeType: 0,
regexPattern: null,
regexCue: null,
regexEnabled: false,
types: ATTRIBUTE_TYPES,
show: true,
attributeKey: '',
@@ -145,13 +177,14 @@ export default {
return this.values.map(item => item.name);
},
isButtonDisabled() {
return this.$v.description.$invalid || this.isMultiselectInvalid;
return this.v$.description.$invalid || this.isMultiselectInvalid;
},
isMultiselectInvalid() {
return (
this.isAttributeTypeList && this.isTouched && this.values.length === 0
);
},
pageTitle() {
return `${this.$t('ATTRIBUTES_MGMT.EDIT.TITLE')} - ${
this.selectedAttribute.attribute_display_name
@@ -165,7 +198,7 @@ export default {
).id;
},
keyErrorMessage() {
if (!this.$v.attributeKey.isKey) {
if (!this.v$.attributeKey.isKey) {
return this.$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.IN_VALID');
}
return this.$t('ATTRIBUTES_MGMT.ADD.FORM.KEY.ERROR');
@@ -173,6 +206,12 @@ export default {
isAttributeTypeList() {
return this.attributeType === 6;
},
isAttributeTypeText() {
return this.attributeType === 0;
},
isRegexEnabled() {
return this.regexEnabled;
},
},
mounted() {
this.setFormValues();
@@ -189,25 +228,38 @@ export default {
this.$refs.tagInput.$el.focus();
},
setFormValues() {
const regexPattern = this.selectedAttribute.regex_pattern
? this.getRegexp(this.selectedAttribute.regex_pattern).source
: null;
this.displayName = this.selectedAttribute.attribute_display_name;
this.description = this.selectedAttribute.attribute_description;
this.attributeType = this.selectedAttributeType;
this.attributeKey = this.selectedAttribute.attribute_key;
this.regexPattern = regexPattern;
this.regexCue = this.selectedAttribute.regex_cue;
this.regexEnabled = regexPattern != null;
this.values = this.setAttributeListValue;
},
async editAttributes() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
if (!this.regexEnabled) {
this.regexPattern = null;
this.regexCue = null;
}
try {
await this.$store.dispatch('attributes/update', {
id: this.selectedAttribute.id,
attribute_description: this.description,
attribute_display_name: this.displayName,
attribute_values: this.updatedAttributeListValues,
regex_pattern: this.regexPattern
? new RegExp(this.regexPattern).toString()
: null,
regex_cue: this.regexCue,
});
this.alertMessage = this.$t('ATTRIBUTES_MGMT.EDIT.API.SUCCESS_MESSAGE');
this.onClose();
} catch (error) {
@@ -218,6 +270,9 @@ export default {
this.showAlert(this.alertMessage);
}
},
toggleRegexEnabled() {
this.regexEnabled = !this.regexEnabled;
},
},
};
</script>
@@ -226,13 +281,16 @@ export default {
padding: 0 var(--space-small) var(--space-small) 0;
font-family: monospace;
}
.multiselect--wrap {
margin-bottom: var(--space-normal);
.error-message {
color: var(--r-400);
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
}
.invalid {
::v-deep {
.multiselect__tags {
@@ -241,13 +299,16 @@ export default {
}
}
}
::v-deep {
.multiselect {
margin-bottom: 0;
}
.multiselect__content-wrapper {
display: none;
}
.multiselect--active .multiselect__tags {
border-radius: var(--border-radius-normal);
}
@@ -7,30 +7,30 @@
v-model="automation.name"
:label="$t('AUTOMATION.ADD.FORM.NAME.LABEL')"
type="text"
:class="{ error: $v.automation.name.$error }"
:class="{ error: v$.automation.name.$error }"
:error="
$v.automation.name.$error
v$.automation.name.$error
? $t('AUTOMATION.ADD.FORM.NAME.ERROR')
: ''
"
:placeholder="$t('AUTOMATION.ADD.FORM.NAME.PLACEHOLDER')"
@blur="$v.automation.name.$touch"
@blur="v$.automation.name.$touch"
/>
<woot-input
v-model="automation.description"
:label="$t('AUTOMATION.ADD.FORM.DESC.LABEL')"
type="text"
:class="{ error: $v.automation.description.$error }"
:class="{ error: v$.automation.description.$error }"
:error="
$v.automation.description.$error
v$.automation.description.$error
? $t('AUTOMATION.ADD.FORM.DESC.ERROR')
: ''
"
:placeholder="$t('AUTOMATION.ADD.FORM.DESC.PLACEHOLDER')"
@blur="$v.automation.description.$touch"
@blur="v$.automation.description.$touch"
/>
<div class="event_wrapper">
<label :class="{ error: $v.automation.event_name.$error }">
<label :class="{ error: v$.automation.event_name.$error }">
{{ $t('AUTOMATION.ADD.FORM.EVENT.LABEL') }}
<select v-model="automation.event_name" @change="onEventChange()">
<option
@@ -41,7 +41,7 @@
{{ event.value }}
</option>
</select>
<span v-if="$v.automation.event_name.$error" class="message">
<span v-if="v$.automation.event_name.$error" class="message">
{{ $t('AUTOMATION.ADD.FORM.EVENT.ERROR') }}
</span>
</label>
@@ -73,7 +73,7 @@
:custom-attribute-type="
getCustomAttributeType(automation.conditions[i].attribute_key)
"
:v="$v.automation.conditions.$each[i]"
:v="v$.automation.conditions.$each[i]"
@resetFilter="resetFilter(i, automation.conditions[i])"
@removeFilter="removeFilter(i)"
/>
@@ -110,7 +110,7 @@
:show-action-input="
showActionInput(automation.actions[i].action_name)
"
:v="$v.automation.actions.$each[i]"
:v="v$.automation.actions.$each[i]"
@resetAction="resetAction(i)"
@removeAction="removeAction(i)"
/>
@@ -228,6 +228,7 @@ export default {
select {
@apply m-0;
}
.info-message {
@apply text-xs text-green-500 dark:text-green-500 text-right;
}
@@ -7,30 +7,30 @@
v-model="automation.name"
:label="$t('AUTOMATION.ADD.FORM.NAME.LABEL')"
type="text"
:class="{ error: $v.automation.name.$error }"
:class="{ error: v$.automation.name.$error }"
:error="
$v.automation.name.$error
v$.automation.name.$error
? $t('AUTOMATION.ADD.FORM.NAME.ERROR')
: ''
"
:placeholder="$t('AUTOMATION.ADD.FORM.NAME.PLACEHOLDER')"
@blur="$v.automation.name.$touch"
@blur="v$.automation.name.$touch"
/>
<woot-input
v-model="automation.description"
:label="$t('AUTOMATION.ADD.FORM.DESC.LABEL')"
type="text"
:class="{ error: $v.automation.description.$error }"
:class="{ error: v$.automation.description.$error }"
:error="
$v.automation.description.$error
v$.automation.description.$error
? $t('AUTOMATION.ADD.FORM.DESC.ERROR')
: ''
"
:placeholder="$t('AUTOMATION.ADD.FORM.DESC.PLACEHOLDER')"
@blur="$v.automation.description.$touch"
@blur="v$.automation.description.$touch"
/>
<div class="event_wrapper">
<label :class="{ error: $v.automation.event_name.$error }">
<label :class="{ error: v$.automation.event_name.$error }">
{{ $t('AUTOMATION.ADD.FORM.EVENT.LABEL') }}
<select v-model="automation.event_name" @change="onEventChange()">
<option
@@ -41,7 +41,7 @@
{{ event.value }}
</option>
</select>
<span v-if="$v.automation.event_name.$error" class="message">
<span v-if="v$.automation.event_name.$error" class="message">
{{ $t('AUTOMATION.ADD.FORM.EVENT.ERROR') }}
</span>
</label>
@@ -70,7 +70,7 @@
getCustomAttributeType(automation.conditions[i].attribute_key)
"
:show-query-operator="i !== automation.conditions.length - 1"
:v="$v.automation.conditions.$each[i]"
:v="v$.automation.conditions.$each[i]"
@resetFilter="resetFilter(i, automation.conditions[i])"
@removeFilter="removeFilter(i)"
/>
@@ -103,7 +103,7 @@
:action-types="automationActionTypes"
:dropdown-values="getActionDropdownValues(action.action_name)"
:show-action-input="showActionInput(action.action_name)"
:v="$v.automation.actions.$each[i]"
:v="v$.automation.actions.$each[i]"
:initial-file-name="getFileName(action, automation.files)"
@resetAction="resetAction(i)"
@removeAction="removeAction(i)"
@@ -206,6 +206,7 @@ export default {
select {
@apply m-0;
}
.info-message {
@apply text-xs text-green-500 dark:text-green-500 text-right;
}
@@ -10,10 +10,10 @@
v-model="title"
:label="$t('CAMPAIGN.ADD.FORM.TITLE.LABEL')"
type="text"
:class="{ error: $v.title.$error }"
:error="$v.title.$error ? $t('CAMPAIGN.ADD.FORM.TITLE.ERROR') : ''"
:class="{ error: v$.title.$error }"
:error="v$.title.$error ? $t('CAMPAIGN.ADD.FORM.TITLE.ERROR') : ''"
:placeholder="$t('CAMPAIGN.ADD.FORM.TITLE.PLACEHOLDER')"
@blur="$v.title.$touch"
@blur="v$.title.$touch"
/>
<div v-if="isOngoingType" class="editor-wrap">
@@ -24,38 +24,38 @@
<woot-message-editor
v-model="message"
class="message-editor"
:class="{ editor_warning: $v.message.$error }"
:class="{ editor_warning: v$.message.$error }"
:placeholder="$t('CAMPAIGN.ADD.FORM.MESSAGE.PLACEHOLDER')"
@blur="$v.message.$touch"
@blur="v$.message.$touch"
/>
<span v-if="$v.message.$error" class="editor-warning__message">
<span v-if="v$.message.$error" class="editor-warning__message">
{{ $t('CAMPAIGN.ADD.FORM.MESSAGE.ERROR') }}
</span>
</div>
</div>
<label v-else :class="{ error: $v.message.$error }">
<label v-else :class="{ error: v$.message.$error }">
{{ $t('CAMPAIGN.ADD.FORM.MESSAGE.LABEL') }}
<textarea
v-model="message"
rows="5"
type="text"
:placeholder="$t('CAMPAIGN.ADD.FORM.MESSAGE.PLACEHOLDER')"
@blur="$v.message.$touch"
@blur="v$.message.$touch"
/>
<span v-if="$v.message.$error" class="message">
<span v-if="v$.message.$error" class="message">
{{ $t('CAMPAIGN.ADD.FORM.MESSAGE.ERROR') }}
</span>
</label>
<label :class="{ error: $v.selectedInbox.$error }">
<label :class="{ error: v$.selectedInbox.$error }">
{{ $t('CAMPAIGN.ADD.FORM.INBOX.LABEL') }}
<select v-model="selectedInbox" @change="onChangeInbox($event)">
<option v-for="item in inboxes" :key="item.name" :value="item.id">
{{ item.name }}
</option>
</select>
<span v-if="$v.selectedInbox.$error" class="message">
<span v-if="v$.selectedInbox.$error" class="message">
{{ $t('CAMPAIGN.ADD.FORM.INBOX.ERROR') }}
</span>
</label>
@@ -63,7 +63,7 @@
<label
v-if="isOnOffType"
class="multiselect-wrap--small"
:class="{ error: $v.selectedAudience.$error }"
:class="{ error: v$.selectedAudience.$error }"
>
{{ $t('CAMPAIGN.ADD.FORM.AUDIENCE.LABEL') }}
<multiselect
@@ -79,17 +79,17 @@
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
@blur="$v.selectedAudience.$touch"
@select="$v.selectedAudience.$touch"
@blur="v$.selectedAudience.$touch"
@select="v$.selectedAudience.$touch"
/>
<span v-if="$v.selectedAudience.$error" class="message">
<span v-if="v$.selectedAudience.$error" class="message">
{{ $t('CAMPAIGN.ADD.FORM.AUDIENCE.ERROR') }}
</span>
</label>
<label
v-if="isOngoingType"
:class="{ error: $v.selectedSender.$error }"
:class="{ error: v$.selectedSender.$error }"
>
{{ $t('CAMPAIGN.ADD.FORM.SENT_BY.LABEL') }}
<select v-model="selectedSender">
@@ -101,7 +101,7 @@
{{ sender.name }}
</option>
</select>
<span v-if="$v.selectedSender.$error" class="message">
<span v-if="v$.selectedSender.$error" class="message">
{{ $t('CAMPAIGN.ADD.FORM.SENT_BY.ERROR') }}
</span>
</label>
@@ -121,26 +121,26 @@
v-model="endPoint"
:label="$t('CAMPAIGN.ADD.FORM.END_POINT.LABEL')"
type="text"
:class="{ error: $v.endPoint.$error }"
:class="{ error: v$.endPoint.$error }"
:error="
$v.endPoint.$error ? $t('CAMPAIGN.ADD.FORM.END_POINT.ERROR') : ''
v$.endPoint.$error ? $t('CAMPAIGN.ADD.FORM.END_POINT.ERROR') : ''
"
:placeholder="$t('CAMPAIGN.ADD.FORM.END_POINT.PLACEHOLDER')"
@blur="$v.endPoint.$touch"
@blur="v$.endPoint.$touch"
/>
<woot-input
v-if="isOngoingType"
v-model="timeOnPage"
:label="$t('CAMPAIGN.ADD.FORM.TIME_ON_PAGE.LABEL')"
type="text"
:class="{ error: $v.timeOnPage.$error }"
:class="{ error: v$.timeOnPage.$error }"
:error="
$v.timeOnPage.$error
v$.timeOnPage.$error
? $t('CAMPAIGN.ADD.FORM.TIME_ON_PAGE.ERROR')
: ''
"
:placeholder="$t('CAMPAIGN.ADD.FORM.TIME_ON_PAGE.PLACEHOLDER')"
@blur="$v.timeOnPage.$touch"
@blur="v$.timeOnPage.$touch"
/>
<label v-if="isOngoingType">
<input
@@ -176,13 +176,14 @@
<script>
import { mapGetters } from 'vuex';
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import alertMixin from 'shared/mixins/alertMixin';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import campaignMixin from 'shared/mixins/campaignMixin';
import WootDateTimePicker from 'dashboard/components/ui/DateTimePicker.vue';
import { URLPattern } from 'urlpattern-polyfill';
import { CAMPAIGNS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -191,6 +192,9 @@ export default {
},
mixins: [alertMixin, campaignMixin],
setup() {
return { v$: useVuelidate() };
},
data() {
return {
title: '',
@@ -343,8 +347,8 @@ export default {
return campaignDetails;
},
async addCampaign() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
try {
@@ -7,10 +7,10 @@
v-model="title"
:label="$t('CAMPAIGN.ADD.FORM.TITLE.LABEL')"
type="text"
:class="{ error: $v.title.$error }"
:error="$v.title.$error ? $t('CAMPAIGN.ADD.FORM.TITLE.ERROR') : ''"
:class="{ error: v$.title.$error }"
:error="v$.title.$error ? $t('CAMPAIGN.ADD.FORM.TITLE.ERROR') : ''"
:placeholder="$t('CAMPAIGN.ADD.FORM.TITLE.PLACEHOLDER')"
@blur="$v.title.$touch"
@blur="v$.title.$touch"
/>
<div class="editor-wrap">
<label>
@@ -20,28 +20,28 @@
v-model="message"
class="message-editor"
:is-format-mode="true"
:class="{ editor_warning: $v.message.$error }"
:class="{ editor_warning: v$.message.$error }"
:placeholder="$t('CAMPAIGN.ADD.FORM.MESSAGE.PLACEHOLDER')"
@input="$v.message.$touch"
@input="v$.message.$touch"
/>
<span v-if="$v.message.$error" class="editor-warning__message">
<span v-if="v$.message.$error" class="editor-warning__message">
{{ $t('CAMPAIGN.ADD.FORM.MESSAGE.ERROR') }}
</span>
</div>
<label :class="{ error: $v.selectedInbox.$error }">
<label :class="{ error: v$.selectedInbox.$error }">
{{ $t('CAMPAIGN.ADD.FORM.INBOX.LABEL') }}
<select v-model="selectedInbox" @change="onChangeInbox($event)">
<option v-for="item in inboxes" :key="item.id" :value="item.id">
{{ item.name }}
</option>
</select>
<span v-if="$v.selectedInbox.$error" class="message">
<span v-if="v$.selectedInbox.$error" class="message">
{{ $t('CAMPAIGN.ADD.FORM.INBOX.ERROR') }}
</span>
</label>
<label :class="{ error: $v.selectedSender.$error }">
<label :class="{ error: v$.selectedSender.$error }">
{{ $t('CAMPAIGN.ADD.FORM.SENT_BY.LABEL') }}
<select v-model="selectedSender">
<option
@@ -52,7 +52,7 @@
{{ sender.name }}
</option>
</select>
<span v-if="$v.selectedSender.$error" class="message">
<span v-if="v$.selectedSender.$error" class="message">
{{ $t('CAMPAIGN.ADD.FORM.SENT_BY.ERROR') }}
</span>
</label>
@@ -60,25 +60,25 @@
v-model="endPoint"
:label="$t('CAMPAIGN.ADD.FORM.END_POINT.LABEL')"
type="text"
:class="{ error: $v.endPoint.$error }"
:class="{ error: v$.endPoint.$error }"
:error="
$v.endPoint.$error ? $t('CAMPAIGN.ADD.FORM.END_POINT.ERROR') : ''
v$.endPoint.$error ? $t('CAMPAIGN.ADD.FORM.END_POINT.ERROR') : ''
"
:placeholder="$t('CAMPAIGN.ADD.FORM.END_POINT.PLACEHOLDER')"
@blur="$v.endPoint.$touch"
@blur="v$.endPoint.$touch"
/>
<woot-input
v-model="timeOnPage"
:label="$t('CAMPAIGN.ADD.FORM.TIME_ON_PAGE.LABEL')"
type="text"
:class="{ error: $v.timeOnPage.$error }"
:class="{ error: v$.timeOnPage.$error }"
:error="
$v.timeOnPage.$error
v$.timeOnPage.$error
? $t('CAMPAIGN.ADD.FORM.TIME_ON_PAGE.ERROR')
: ''
"
:placeholder="$t('CAMPAIGN.ADD.FORM.TIME_ON_PAGE.PLACEHOLDER')"
@blur="$v.timeOnPage.$touch"
@blur="v$.timeOnPage.$touch"
/>
<label>
<input
@@ -113,11 +113,12 @@
<script>
import { mapGetters } from 'vuex';
import { required } from 'vuelidate/lib/validators';
import { required } from '@vuelidate/validators';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import alertMixin from 'shared/mixins/alertMixin';
import campaignMixin from 'shared/mixins/campaignMixin';
import { URLPattern } from 'urlpattern-polyfill';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -130,6 +131,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
title: '',
@@ -252,8 +256,8 @@ export default {
this.loadInboxMembers();
},
async editCampaign() {
this.$v.$touch();
if (this.$v.$invalid) {
this.v$.$touch();
if (this.v$.$invalid) {
return;
}
try {
@@ -7,38 +7,38 @@
/>
<form class="flex flex-col w-full" @submit.prevent="addCannedResponse()">
<div class="w-full">
<label :class="{ error: $v.shortCode.$error }">
<label :class="{ error: v$.shortCode.$error }">
{{ $t('CANNED_MGMT.ADD.FORM.SHORT_CODE.LABEL') }}
<input
v-model.trim="shortCode"
type="text"
:placeholder="$t('CANNED_MGMT.ADD.FORM.SHORT_CODE.PLACEHOLDER')"
@input="$v.shortCode.$touch"
@input="v$.shortCode.$touch"
/>
</label>
</div>
<div class="w-full">
<label :class="{ error: $v.content.$error }">
<label :class="{ error: v$.content.$error }">
{{ $t('CANNED_MGMT.ADD.FORM.CONTENT.LABEL') }}
</label>
<div class="editor-wrap">
<woot-message-editor
v-model="content"
class="message-editor"
:class="{ editor_warning: $v.content.$error }"
:class="{ editor_warning: v$.content.$error }"
:enable-variables="true"
:enable-canned-responses="false"
:placeholder="$t('CANNED_MGMT.ADD.FORM.CONTENT.PLACEHOLDER')"
@blur="$v.content.$touch"
@blur="v$.content.$touch"
/>
</div>
</div>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-submit-button
:disabled="
$v.content.$invalid ||
$v.shortCode.$invalid ||
v$.content.$invalid ||
v$.shortCode.$invalid ||
addCanned.showLoading
"
:button-text="$t('CANNED_MGMT.ADD.FORM.SUBMIT')"
@@ -54,12 +54,13 @@
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import WootSubmitButton from '../../../../components/buttons/FormSubmitButton.vue';
import Modal from '../../../../components/Modal.vue';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -78,6 +79,9 @@ export default {
default: () => {},
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
shortCode: '',
@@ -102,8 +106,8 @@ export default {
resetForm() {
this.shortCode = '';
this.content = '';
this.$v.shortCode.$reset();
this.$v.content.$reset();
this.v$.shortCode.$reset();
this.v$.content.$reset();
},
addCannedResponse() {
// Show loading on button
@@ -4,38 +4,38 @@
<woot-modal-header :header-title="pageTitle" />
<form class="flex flex-col w-full" @submit.prevent="editCannedResponse()">
<div class="w-full">
<label :class="{ error: $v.shortCode.$error }">
<label :class="{ error: v$.shortCode.$error }">
{{ $t('CANNED_MGMT.EDIT.FORM.SHORT_CODE.LABEL') }}
<input
v-model.trim="shortCode"
type="text"
:placeholder="$t('CANNED_MGMT.EDIT.FORM.SHORT_CODE.PLACEHOLDER')"
@input="$v.shortCode.$touch"
@input="v$.shortCode.$touch"
/>
</label>
</div>
<div class="w-full">
<label :class="{ error: $v.content.$error }">
<label :class="{ error: v$.content.$error }">
{{ $t('CANNED_MGMT.EDIT.FORM.CONTENT.LABEL') }}
</label>
<div class="editor-wrap">
<woot-message-editor
v-model="content"
class="message-editor"
:class="{ editor_warning: $v.content.$error }"
:class="{ editor_warning: v$.content.$error }"
:enable-variables="true"
:enable-canned-responses="false"
:placeholder="$t('CANNED_MGMT.EDIT.FORM.CONTENT.PLACEHOLDER')"
@blur="$v.content.$touch"
@blur="v$.content.$touch"
/>
</div>
</div>
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
<woot-submit-button
:disabled="
$v.content.$invalid ||
$v.shortCode.$invalid ||
v$.content.$invalid ||
v$.shortCode.$invalid ||
editCanned.showLoading
"
:button-text="$t('CANNED_MGMT.EDIT.FORM.SUBMIT')"
@@ -52,11 +52,12 @@
<script>
/* eslint no-console: 0 */
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import WootSubmitButton from '../../../../components/buttons/FormSubmitButton.vue';
import alertMixin from 'shared/mixins/alertMixin';
import Modal from '../../../../components/Modal.vue';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -71,6 +72,9 @@ export default {
edshortCode: { type: String, default: '' },
onClose: { type: Function, default: () => {} },
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
editCanned: {
@@ -98,14 +102,14 @@ export default {
},
methods: {
setPageName({ name }) {
this.$v.content.$touch();
this.v$.content.$touch();
this.content = name;
},
resetForm() {
this.shortCode = '';
this.content = '';
this.$v.shortCode.$reset();
this.$v.content.$reset();
this.v$.shortCode.$reset();
this.v$.content.$reset();
},
editCannedResponse() {
// Show loading on button
@@ -9,7 +9,7 @@
</div>
<div class="w-[60%]">
<div class="w-full">
<label :class="{ error: $v.selectedAgents.$error }">
<label :class="{ error: v$.selectedAgents.$error }">
{{ $t('INBOX_MGMT.ADD.AGENTS.TITLE') }}
<multiselect
v-model="selectedAgents"
@@ -24,9 +24,9 @@
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
:placeholder="$t('INBOX_MGMT.ADD.AGENTS.PICK_AGENTS')"
@select="$v.selectedAgents.$touch"
@select="v$.selectedAgents.$touch"
/>
<span v-if="$v.selectedAgents.$error" class="message">
<span v-if="v$.selectedAgents.$error" class="message">
{{ $t('INBOX_MGMT.ADD.AGENTS.VALIDATION_ERROR') }}
</span>
</label>
@@ -18,37 +18,37 @@
<div v-if="isIMAPEnabled" class="imap-details-wrap">
<woot-input
v-model.trim="address"
:class="{ error: $v.address.$error }"
:class="{ error: v$.address.$error }"
class="medium-9 columns"
:label="$t('INBOX_MGMT.IMAP.ADDRESS.LABEL')"
:placeholder="$t('INBOX_MGMT.IMAP.ADDRESS.PLACE_HOLDER')"
@blur="$v.address.$touch"
@blur="v$.address.$touch"
/>
<woot-input
v-model="port"
type="number"
:class="{ error: $v.port.$error }"
:class="{ error: v$.port.$error }"
class="medium-9 columns"
:label="$t('INBOX_MGMT.IMAP.PORT.LABEL')"
:placeholder="$t('INBOX_MGMT.IMAP.PORT.PLACE_HOLDER')"
@blur="$v.port.$touch"
@blur="v$.port.$touch"
/>
<woot-input
v-model="login"
:class="{ error: $v.login.$error }"
:class="{ error: v$.login.$error }"
class="medium-9 columns"
:label="$t('INBOX_MGMT.IMAP.LOGIN.LABEL')"
:placeholder="$t('INBOX_MGMT.IMAP.LOGIN.PLACE_HOLDER')"
@blur="$v.login.$touch"
@blur="v$.login.$touch"
/>
<woot-input
v-model="password"
:class="{ error: $v.password.$error }"
:class="{ error: v$.password.$error }"
class="medium-9 columns"
:label="$t('INBOX_MGMT.IMAP.PASSWORD.LABEL')"
:placeholder="$t('INBOX_MGMT.IMAP.PASSWORD.PLACE_HOLDER')"
type="password"
@blur="$v.password.$touch"
@blur="v$.password.$touch"
/>
<label for="toggle-enable-ssl">
<input
@@ -62,7 +62,7 @@
<woot-submit-button
:button-text="$t('INBOX_MGMT.IMAP.UPDATE')"
:loading="uiFlags.isUpdatingIMAP"
:disabled="($v.$invalid && isIMAPEnabled) || uiFlags.isUpdatingIMAP"
:disabled="(v$.$invalid && isIMAPEnabled) || uiFlags.isUpdatingIMAP"
/>
</form>
</settings-section>
@@ -73,7 +73,8 @@
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import SettingsSection from 'dashboard/components/SettingsSection.vue';
import { required, minLength } from 'vuelidate/lib/validators';
import { required, minLength } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
export default {
components: {
@@ -86,6 +87,9 @@ export default {
default: () => ({}),
},
},
setup() {
return { v$: useVuelidate() };
},
data() {
return {
isIMAPEnabled: false,
@@ -38,21 +38,21 @@
<woot-input
v-model.trim="selectedInboxName"
class="w-[75%] pb-4"
:class="{ error: $v.selectedInboxName.$error }"
:class="{ error: v$.selectedInboxName.$error }"
:label="inboxNameLabel"
:placeholder="inboxNamePlaceHolder"
:error="
$v.selectedInboxName.$error
v$.selectedInboxName.$error
? $t('INBOX_MGMT.ADD.CHANNEL_NAME.ERROR')
: ''
"
@blur="$v.selectedInboxName.$touch"
@blur="v$.selectedInboxName.$touch"
/>
<woot-input
v-if="isAPIInbox"
v-model.trim="webhookUrl"
class="w-[75%] pb-4"
:class="{ error: $v.webhookUrl.$error }"
:class="{ error: v$.webhookUrl.$error }"
:label="
$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.LABEL')
"
@@ -60,11 +60,11 @@
$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.PLACEHOLDER')
"
:error="
$v.webhookUrl.$error
v$.webhookUrl.$error
? $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.ERROR')
: ''
"
@blur="$v.webhookUrl.$touch"
@blur="v$.webhookUrl.$touch"
/>
<woot-input
v-if="isAWebWidgetInbox"
@@ -383,7 +383,7 @@
<woot-submit-button
v-if="isAPIInbox"
type="submit"
:disabled="$v.webhookUrl.$invalid"
:disabled="v$.webhookUrl.$invalid"
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
:loading="uiFlags.isUpdating"
@click="updateInbox"
@@ -391,7 +391,7 @@
<woot-submit-button
v-else
type="submit"
:disabled="$v.$invalid"
:disabled="v$.$invalid"
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
:loading="uiFlags.isUpdating"
@click="updateInbox"

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