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 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
613 changed files with 5486 additions and 21570 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ jobs:
name: yarn name: yarn
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
# Store yarn / webpacker cache # Store yarn cache
- save_cache: - save_cache:
key: chatwoot-yarn-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }} key: chatwoot-yarn-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }}
paths: 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 '/POSTGRES_HOST/ s/=.*/=localhost/' .env
sed -i -e '/SMTP_ADDRESS/ 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 "/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 sed -i -e "/DEV_SERVER_PUBLIC/ s/=.*/=https:\/\/$CODESPACE_NAME-3035.githubpreview.dev/" .env
# uncomment the webpacker env variable # uncomment the dev server env variable
sed -i -e '/WEBPACKER_DEV_SERVER_PUBLIC/s/^# //' .env sed -i -e '/DEV_SERVER_PUBLIC/s/^# //' .env
# fix the error with webpacker # fix the error with dev server
echo 'export NODE_OPTIONS=--openssl-legacy-provider' >> ~/.zshrc echo 'export NODE_OPTIONS=--openssl-legacy-provider' >> ~/.zshrc
# codespaces make the ports public # 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 # if you want to use letter_opener for local emails
# LETTER_OPENER=true # LETTER_OPENER=true
# meant to be used in github codespaces # meant to be used in github codespaces
# WEBPACKER_DEV_SERVER_PUBLIC= # DEV_SERVER_PUBLIC=
# If you want to use official mobile app, # If you want to use official mobile app,
# the notifications would be relayed via a Chatwoot server # the notifications would be relayed via a Chatwoot server
+2 -10
View File
@@ -7,11 +7,10 @@ module.exports = {
'plugin:cypress/recommended', 'plugin:cypress/recommended',
], ],
parserOptions: { parserOptions: {
parser: '@babel/eslint-parser', ecmaVersion: 2022,
ecmaVersion: 2020,
sourceType: 'module', sourceType: 'module',
}, },
plugins: ['html', 'prettier', 'babel'], plugins: ['html', 'prettier'],
rules: { rules: {
'prettier/prettier': ['error'], 'prettier/prettier': ['error'],
camelcase: 'off', camelcase: 'off',
@@ -56,13 +55,6 @@ module.exports = {
'import/extensions': ['off'], 'import/extensions': ['off'],
'no-console': 'error', 'no-console': 'error',
}, },
settings: {
'import/resolver': {
webpack: {
config: 'config/webpack/resolve.js',
},
},
},
env: { env: {
browser: true, browser: true,
jest: true, jest: true,
+8
View File
@@ -76,3 +76,11 @@ yarn-debug.log*
.yarn-integrity .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
+70 -11
View File
@@ -2,6 +2,7 @@ require:
- rubocop-performance - rubocop-performance
- rubocop-rails - rubocop-rails
- rubocop-rspec - rubocop-rspec
inherit_from: .rubocop_todo.yml
Layout/LineLength: Layout/LineLength:
Max: 150 Max: 150
@@ -11,8 +12,7 @@ Metrics/ClassLength:
Exclude: Exclude:
- 'app/models/message.rb' - 'app/models/message.rb'
- 'app/models/conversation.rb' - 'app/models/conversation.rb'
Metrics/MethodLength:
Max: 19
RSpec/ExampleLength: RSpec/ExampleLength:
Max: 25 Max: 25
Style/Documentation: Style/Documentation:
@@ -50,7 +50,6 @@ Lint/OrAssignmentToConstant:
Exclude: Exclude:
- 'lib/redis/config.rb' - 'lib/redis/config.rb'
Metrics/BlockLength: Metrics/BlockLength:
Max: 30
Exclude: Exclude:
- spec/**/* - spec/**/*
- '**/routes.rb' - '**/routes.rb'
@@ -103,31 +102,84 @@ RSpec/FactoryBot/SyntaxMethods:
Enabled: false Enabled: false
Naming/VariableNumber: Naming/VariableNumber:
Enabled: false Enabled: false
Naming/MemoizedInstanceVariableName: Metrics/MethodLength:
Exclude: Exclude:
- 'app/models/message.rb' - 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
- 'db/migrate/20211219031453_update_foreign_keys_on_delete.rb'
Rails/CreateTableWithTimestamps:
Exclude:
- 'db/migrate/20170207092002_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb'
Style/GuardClause: Style/GuardClause:
Exclude: Exclude:
- 'app/builders/account_builder.rb' - 'app/builders/account_builder.rb'
- 'app/models/attachment.rb' - 'app/models/attachment.rb'
- 'app/models/message.rb' - 'app/models/message.rb'
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Metrics/AbcSize: Metrics/AbcSize:
Max: 26
Exclude: Exclude:
- 'app/controllers/concerns/auth_helper.rb' - 'app/controllers/concerns/auth_helper.rb'
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
- 'app/controllers/api/v1/accounts/inboxes_controller.rb'
- 'db/migrate/20211219031453_update_foreign_keys_on_delete.rb'
Metrics/CyclomaticComplexity:
Max: 7
Exclude:
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
- 'db/migrate/20161025070645_remchannel.rb'
- 'db/migrate/20161025070645_remchannel.rb'
- 'db/migrate/20161110102609_removeinboxid.rb'
- 'db/migrate/20170519091539_add_avatar_to_fb.rb'
- 'db/migrate/20191020085608_rename_old_tables.rb'
- 'db/migrate/20191126185833_update_user_invite_foreign_key.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
- 'db/migrate/20210513083044_remove_not_null_from_webhook_url_channel_api.rb'
Rails/BulkChangeTable:
Exclude:
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
- 'db/migrate/20200121190901_create_account_users.rb'
- 'db/migrate/20170211092540_notnullableusers.rb'
- 'db/migrate/20170403095203_contactadder.rb'
- 'db/migrate/20170406104018_add_default_status_conv.rb'
- 'db/migrate/20170511134418_latlong.rb'
- 'db/migrate/20191027054756_create_contact_inboxes.rb'
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
- 'db/migrate/20210425093724_convert_integration_hook_settings_field.rb'
Rails/UniqueValidationWithoutIndex: Rails/UniqueValidationWithoutIndex:
Exclude: Exclude:
- 'app/models/channel/twitter_profile.rb' - 'app/models/channel/twitter_profile.rb'
- 'app/models/webhook.rb' - 'app/models/webhook.rb'
- 'app/models/contact.rb' - 'app/models/contact.rb'
- 'app/models/integrations/hook.rb' - 'app/models/integrations/hook.rb'
- 'app/models/canned_response.rb'
- 'app/models/telegram_bot.rb'
Rails/RenderInline: Rails/RenderInline:
Exclude: Exclude:
- 'app/controllers/swagger_controller.rb' - 'app/controllers/swagger_controller.rb'
Performance/CollectionLiteralInLoop:
Exclude:
- 'db/migrate/20210315101919_enable_email_channel.rb'
Rails/ThreeStateBooleanColumn: Rails/ThreeStateBooleanColumn:
Exclude: Exclude:
- 'db/migrate/20200509044639_add_hide_input_flag_to_bot_config.rb'
- 'db/migrate/20200605130625_agent_away_message_to_auto_reply.rb'
- 'db/migrate/20200606132552_create_labels.rb'
- 'db/migrate/20201027135006_create_working_hours.rb'
- 'db/migrate/20210112174124_add_hmac_token_to_inbox.rb'
- 'db/migrate/20210114202310_create_teams.rb'
- 'db/migrate/20210212154240_add_request_for_email_on_channel_web_widget.rb'
- 'db/migrate/20210428135041_add_campaigns.rb'
- 'db/migrate/20210602182058_add_hmac_to_api_channel.rb'
- 'db/migrate/20210609133433_add_email_collect_to_inboxes.rb'
- 'db/migrate/20210618095823_add_csat_toggle_for_inbox.rb'
- 'db/migrate/20210927062350_add_trigger_only_during_business_hours_collect_to_campaigns.rb'
- 'db/migrate/20211027073553_add_imap_smtp_config_to_channel_email.rb'
- 'db/migrate/20211109143122_add_tweet_enabled_flag_to_twitter_channel.rb'
- 'db/migrate/20211216110209_add_allow_messages_after_resolved_to_inbox.rb'
- 'db/migrate/20220116103902_add_open_ssl_verify_mode_to_channel_email.rb'
- 'db/migrate/20220216151613_add_open_all_day_to_working_hour.rb'
- 'db/migrate/20220511072655_add_archive_column_to_portal.rb'
- 'db/migrate/20230503101201_create_sla_policies.rb' - 'db/migrate/20230503101201_create_sla_policies.rb'
RSpec/IndexedLet: RSpec/IndexedLet:
Enabled: false Enabled: false
@@ -135,8 +187,6 @@ RSpec/NamedSubject:
Enabled: false Enabled: false
# we should bring this down # we should bring this down
RSpec/MultipleExpectations:
Max: 7
RSpec/MultipleMemoizedHelpers: RSpec/MultipleMemoizedHelpers:
Max: 14 Max: 14
@@ -153,4 +203,13 @@ AllCops:
- 'config/environments/**/*' - 'config/environments/**/*'
- 'tmp/**/*' - 'tmp/**/*'
- 'storage/**/*' - 'storage/**/*'
- 'db/migrate/20230426130150_init_schema.rb' - 'db/migrate/20200225162150_init_schema.rb'
- 'db/migrate/20210611180222_create_active_storage_variant_records.active_storage.rb'
- 'db/migrate/20210611180221_add_service_name_to_active_storage_blobs.active_storage.rb'
- db/migrate/20200309213132_add_account_id_to_agent_bot_inboxes.rb
- db/migrate/20200331095710_add_identifier_to_contact.rb
- db/migrate/20200429082655_add_medium_to_twilio_sms.rb
- db/migrate/20200503151130_add_account_feature_flag.rb
- db/migrate/20200927135222_add_last_activity_at_to_conversation.rb
- db/migrate/20210306170117_add_last_activity_at_to_contacts.rb
- db/migrate/20220809104508_revert_cascading_indexes.rb
+287
View File
@@ -0,0 +1,287 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-10-23 16:47:02 +0530 using RuboCop version 0.73.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
Lint/DuplicateMethods:
Exclude:
- 'app/controllers/api/v1/reports_controller.rb'
# Offense count: 1
Lint/RescueException:
Exclude:
- 'app/builders/messages/message_builder.rb'
# Offense count: 4
Lint/ShadowingOuterLocalVariable:
Exclude:
- 'app/controllers/api/v1/reports_controller.rb'
# Offense count: 3
# Configuration parameters: AllowKeywordBlockArguments.
Lint/UnderscorePrefixedVariableName:
Exclude:
- 'app/models/account.rb'
- 'deploy/before_symlink.rb'
# Offense count: 18
Lint/UselessAssignment:
Exclude:
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/facebook_indicators_controller.rb'
- 'app/listeners/action_cable_listener.rb'
- 'app/listeners/reporting_listener.rb'
- 'app/models/channel/facebook_page.rb'
- 'app/models/facebook_page.rb'
# Offense count: 14
Metrics/AbcSize:
Max: 26
# Offense count: 1
# Configuration parameters: CountComments, ExcludedMethods.
# ExcludedMethods: refine
Metrics/BlockLength:
Max: 30
# Offense count: 2
Metrics/CyclomaticComplexity:
Max: 7
# Offense count: 10
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/MethodLength:
Max: 19
# Offense count: 1
Metrics/PerceivedComplexity:
Max: 8
# Offense count: 6
Naming/AccessorMethodName:
Exclude:
- 'app/builders/report_builder.rb'
- 'app/controllers/api/v1/accounts_controller.rb'
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/conversations_controller.rb'
# Offense count: 9
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
Naming/MemoizedInstanceVariableName:
Exclude:
- 'app/controllers/api/base_controller.rb'
- 'app/controllers/api/v1/conversations_controller.rb'
- 'app/controllers/api/v1/webhooks_controller.rb'
- 'app/controllers/application_controller.rb'
- 'app/models/message.rb'
- 'lib/integrations/widget/outgoing_message_builder.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: MaxKeyValuePairs.
Performance/RedundantMerge:
Exclude:
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/models/message.rb'
# Offense count: 1
# Cop supports --auto-correct.
Performance/StringReplacement:
Exclude:
- 'lib/events/base.rb'
# Offense count: 4
# Configuration parameters: Prefixes.
# Prefixes: when, with, without
RSpec/ContextWording:
Exclude:
- 'spec/models/contact_spec.rb'
- 'spec/models/user_spec.rb'
# Offense count: 1
RSpec/DescribeClass:
Exclude:
- 'spec/mailers/confirmation_instructions_spec.rb'
# Offense count: 1
RSpec/DescribeSymbol:
Exclude:
- 'spec/mailers/confirmation_instructions_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AllowConsecutiveOneLiners.
RSpec/EmptyLineAfterExample:
Exclude:
- 'spec/models/user_spec.rb'
# Offense count: 1
# Configuration parameters: Max.
RSpec/ExampleLength:
Exclude:
- 'spec/models/conversation_spec.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_line_only, single_statement_only, disallow
RSpec/ImplicitSubject:
Exclude:
- 'spec/models/user_spec.rb'
# Offense count: 7
# Configuration parameters: AggregateFailuresByDefault.
RSpec/MultipleExpectations:
Max: 7
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: not_to, to_not
RSpec/NotToNot:
Exclude:
- 'spec/mailers/confirmation_instructions_spec.rb'
# Offense count: 1
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Exclude:
- 'spec/models/conversation_spec.rb'
# Offense count: 4
# Cop supports --auto-correct.
Rails/ActiveRecordAliases:
Exclude:
- 'app/controllers/api/v1/agents_controller.rb'
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/canned_responses_controller.rb'
- 'app/controllers/api/v1/contacts_controller.rb'
# Offense count: 2
# Cop supports --auto-correct.
Rails/BelongsTo:
Exclude:
- 'app/models/message.rb'
- 'app/models/user.rb'
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/EnumHash:
Exclude:
- 'app/models/attachment.rb'
- 'app/models/conversation.rb'
- 'app/models/message.rb'
- 'app/models/user.rb'
# Offense count: 1
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasManyOrHasOneDependent:
Exclude:
- 'app/models/user.rb'
# Offense count: 1
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/InverseOf:
Exclude:
- 'app/models/user.rb'
# Offense count: 1
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb
Rails/LexicallyScopedActionFilter:
Exclude:
- 'app/controllers/home_controller.rb'
# Offense count: 2
# Configuration parameters: Include.
# Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb
Rails/Output:
Exclude:
- 'app/bot/bot.rb'
- 'app/builders/account_builder.rb'
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: strict, flexible
Rails/TimeZone:
Exclude:
- 'app/builders/report_builder.rb'
- 'lib/reports/update_account_identity.rb'
- 'lib/reports/update_agent_identity.rb'
- 'lib/reports/update_identity.rb'
- 'spec/models/conversation_spec.rb'
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/Validation:
Exclude:
- 'app/models/canned_response.rb'
- 'app/models/channel/facebook_page.rb'
- 'app/models/facebook_page.rb'
- 'app/models/telegram_bot.rb'
- 'app/models/user.rb'
# Offense count: 15
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: nested, compact
Style/ClassAndModuleChildren:
Exclude:
- 'app/builders/messages/message_builder.rb'
- 'app/controllers/api/v1/inbox_members_controller.rb'
- 'app/models/channel/facebook_page.rb'
- 'app/models/channel/web_widget.rb'
- 'app/presenters/conversations/event_data_presenter.rb'
- 'app/services/facebook/send_reply_service.rb'
- 'lib/integrations/facebook/delivery_status.rb'
- 'lib/integrations/facebook/message_creator.rb'
- 'lib/integrations/facebook/message_parser.rb'
- 'lib/integrations/widget/incoming_message_builder.rb'
# Offense count: 4
Style/CommentedKeyword:
Exclude:
- 'app/controllers/api/v1/callbacks_controller.rb'
- 'app/controllers/api/v1/conversations/assignments_controller.rb'
- 'app/controllers/api/v1/conversations/labels_controller.rb'
- 'app/controllers/api/v1/labels_controller.rb'
# Offense count: 1
# Configuration parameters: AllowIfModifier.
Style/IfInsideElse:
Exclude:
- 'app/finders/conversation_finder.rb'
# Offense count: 1
Style/MixinUsage:
Exclude:
- 'app/bot/bot.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
- 'spec/**/*'
- 'app/controllers/api/v1/callbacks_controller.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, explicit
Style/RescueStandardError:
Exclude:
- 'app/models/channel/facebook_page.rb'
+1 -3
View File
@@ -2,7 +2,6 @@ import { addDecorator } from '@storybook/vue';
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import VueI18n from 'vue-i18n'; import VueI18n from 'vue-i18n';
import Vuelidate from 'vuelidate';
import Multiselect from 'vue-multiselect'; import Multiselect from 'vue-multiselect';
import VueDOMPurifyHTML from 'vue-dompurify-html'; import VueDOMPurifyHTML from 'vue-dompurify-html';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon'; 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'; import '../app/javascript/dashboard/assets/scss/storybook.scss';
Vue.use(VueI18n); Vue.use(VueI18n);
Vue.use(Vuelidate);
Vue.use(WootUiKit); Vue.use(WootUiKit);
Vue.use(Vuex); Vue.use(Vuex);
Vue.use(VueDOMPurifyHTML, domPurifyConfig); Vue.use(VueDOMPurifyHTML, domPurifyConfig);
@@ -32,7 +30,7 @@ addDecorator(() => ({
template: '<story/>', template: '<story/>',
i18n: i18nConfig, i18n: i18nConfig,
store, store,
beforeCreate: function() { beforeCreate: function () {
this.$root._i18n = this.$i18n; this.$root._i18n = this.$i18n;
}, },
})); }));
+1 -1
View File
@@ -64,7 +64,7 @@ gem 'activerecord-import'
gem 'dotenv-rails' gem 'dotenv-rails'
gem 'foreman' gem 'foreman'
gem 'puma' gem 'puma'
gem 'webpacker' gem 'vite_rails'
# metrics on heroku # metrics on heroku
gem 'barnes' gem 'barnes'
+16 -14
View File
@@ -216,6 +216,7 @@ GEM
railties (>= 3.2) railties (>= 3.2)
down (5.4.0) down (5.4.0)
addressable (~> 2.8) addressable (~> 2.8)
dry-cli (1.0.0)
ecma-re-validator (0.4.0) ecma-re-validator (0.4.0)
regexp_parser (~> 2.2) regexp_parser (~> 2.2)
elastic-apm (4.6.2) elastic-apm (4.6.2)
@@ -277,7 +278,7 @@ GEM
gli (2.21.1) gli (2.21.1)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
gmail_xoauth (0.4.3) gmail_xoauth (0.4.2)
oauth (>= 0.3.6) oauth (>= 0.3.6)
google-apis-core (0.11.0) google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
@@ -488,14 +489,14 @@ GEM
newrelic_rpm (9.6.0) newrelic_rpm (9.6.0)
base64 base64
nio4r (2.7.0) nio4r (2.7.0)
nokogiri (1.16.2) nokogiri (1.16.0)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.2-arm64-darwin) nokogiri (1.16.0-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.2-x86_64-darwin) nokogiri (1.16.0-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.2-x86_64-linux) nokogiri (1.16.0-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
numo-narray (0.9.2.1) numo-narray (0.9.2.1)
oauth (1.1.0) oauth (1.1.0)
@@ -570,7 +571,7 @@ GEM
rack (>= 1.2.0) rack (>= 1.2.0)
rack-protection (3.1.0) rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4) rack (~> 2.2, >= 2.2.4)
rack-proxy (0.7.6) rack-proxy (0.7.7)
rack rack
rack-test (2.1.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
@@ -708,7 +709,6 @@ GEM
activerecord (>= 4) activerecord (>= 4)
activesupport (>= 4) activesupport (>= 4)
selectize-rails (0.12.6) selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.14.0) sentry-rails (5.14.0)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby (~> 5.14.0) sentry-ruby (~> 5.14.0)
@@ -794,7 +794,14 @@ GEM
valid_email2 (4.0.6) valid_email2 (4.0.6)
activemodel (>= 3.2) activemodel (>= 3.2)
mail (~> 2.5) mail (~> 2.5)
version_gem (1.1.3) 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) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.2.1) web-console (4.2.1)
@@ -810,11 +817,6 @@ GEM
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) 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) webrick (1.8.1)
websocket-driver (0.7.6) websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
@@ -957,10 +959,10 @@ DEPENDENCIES
tzinfo-data tzinfo-data
uglifier uglifier
valid_email2 valid_email2
vite_rails
web-console (>= 4.2.1) web-console (>= 4.2.1)
web-push web-push
webmock webmock
webpacker
wisper (= 2.0.0) wisper (= 2.0.0)
working_hours working_hours
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2017-2024 Chatwoot Inc. Copyright (c) 2017-2021 Chatwoot Inc.
Portions of this software are licensed as follows: Portions of this software are licensed as follows:
+1 -1
View File
@@ -1,4 +1,4 @@
backend: bin/rails s -p 3000 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 # https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml 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 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 worker: RAILS_ENV=test dotenv bundle exec sidekiq -C config/sidekiq.yml
@@ -9,7 +9,7 @@
padding: 4px 12px; padding: 4px 12px;
.icon-container { .icon-container {
margin-right: 2px; margin-right: 4px;
} }
@@ -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-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; $swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default;
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions // Transitions
$transition-ease-in: all 0.250s ease-in; $transition-ease-in: all 0.250s ease-in;
+4 -14
View File
@@ -2,7 +2,7 @@
class AccountBuilder class AccountBuilder
include CustomExceptions::Account include CustomExceptions::Account
pattr_initialize [:account_name, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale] pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale]
def perform def perform
if @user.nil? if @user.nil?
@@ -15,22 +15,12 @@ class AccountBuilder
end end
[@user, @account] [@user, @account]
rescue StandardError => e rescue StandardError => e
Rails.logger.debug e.inspect puts e.inspect
raise e raise e
end end
private private
def user_full_name
# the empty string ensures that not-null constraint is not violated
@user_full_name || ''
end
def account_name
# the empty string ensures that not-null constraint is not violated
@account_name || ''
end
def validate_email def validate_email
address = ValidEmail2::Address.new(@email) address = ValidEmail2::Address.new(@email)
if address.valid? # && !address.disposable? if address.valid? # && !address.disposable?
@@ -49,7 +39,7 @@ class AccountBuilder
end end
def create_account def create_account
@account = Account.create!(name: account_name, locale: I18n.locale) @account = Account.create!(name: @account_name, locale: I18n.locale)
Current.account = @account Current.account = @account
end end
@@ -74,7 +64,7 @@ class AccountBuilder
@user = User.new(email: @email, @user = User.new(email: @email,
password: user_password, password: user_password,
password_confirmation: user_password, password_confirmation: user_password,
name: user_full_name) name: @user_full_name)
@user.type = 'SuperAdmin' if @super_admin @user.type = 'SuperAdmin' if @super_admin
@user.confirm if @confirmed @user.confirm if @confirmed
@user.save! @user.save!
-60
View File
@@ -1,60 +0,0 @@
# The AgentBuilder class is responsible for creating a new agent.
# It initializes with necessary attributes and provides a perform method
# to create a user and account user in a transaction.
class AgentBuilder
# Initializes an AgentBuilder with necessary attributes.
# @param email [String] the email of the user.
# @param name [String] the name of the user.
# @param role [String] the role of the user, defaults to 'agent' if not provided.
# @param inviter [User] the user who is inviting the agent (Current.user in most cases).
# @param availability [String] the availability status of the user, defaults to 'offline' if not provided.
# @param auto_offline [Boolean] the auto offline status of the user.
pattr_initialize [:email, { name: '' }, :inviter, :account, { role: :agent }, { availability: :offline }, { auto_offline: false }]
# Creates a user and account user in a transaction.
# @return [User] the created user.
def perform
ActiveRecord::Base.transaction do
@user = find_or_create_user
send_confirmation_if_required
create_account_user
end
@user
end
private
# Finds a user by email or creates a new one with a temporary password.
# @return [User] the found or created user.
def find_or_create_user
user = User.find_by(email: email)
return user if user
temp_password = "1!aA#{SecureRandom.alphanumeric(12)}"
User.create!(email: email, name: name, password: temp_password, password_confirmation: temp_password)
end
# Sends confirmation instructions if the user is persisted and not confirmed.
def send_confirmation_if_required
@user.send_confirmation_instructions if user_needs_confirmation?
end
# Checks if the user needs confirmation.
# @return [Boolean] true if the user is persisted and not confirmed, false otherwise.
def user_needs_confirmation?
@user.persisted? && !@user.confirmed?
end
# Creates an account user linking the user to the current account.
def create_account_user
AccountUser.create!({
account_id: account.id,
user_id: @user.id,
inviter_id: inviter.id
}.merge({
role: role,
availability: availability,
auto_offline: auto_offline
}.compact))
end
end
@@ -1,26 +1,16 @@
class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
before_action :fetch_agent, except: [:create, :index, :bulk_create] before_action :fetch_agent, except: [:create, :index]
before_action :check_authorization before_action :check_authorization
before_action :find_user, only: [:create]
before_action :validate_limit, only: [:create] before_action :validate_limit, only: [:create]
before_action :validate_limit_for_bulk_create, only: [:bulk_create] before_action :create_user, only: [:create]
before_action :save_account_user, only: [:create]
def index def index
@agents = agents @agents = agents
end end
def create def create; end
builder = AgentBuilder.new(
email: new_agent_params['email'],
name: new_agent_params['name'],
role: new_agent_params['role'],
availability: new_agent_params['availability'],
auto_offline: new_agent_params['auto_offline'],
inviter: current_user,
account: Current.account
)
builder.perform
end
def update def update
@agent.update!(agent_params.slice(:name).compact) @agent.update!(agent_params.slice(:name).compact)
@@ -33,21 +23,6 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
head :ok head :ok
end end
def bulk_create
emails = params[:emails]
emails.each do |email|
builder = AgentBuilder.new(
email: email,
name: email.split('@').first,
inviter: current_user,
account: Current.account
)
builder.perform
end
head :ok
end
private private
def check_authorization def check_authorization
@@ -58,34 +33,47 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
@agent = agents.find(params[:id]) @agent = agents.find(params[:id])
end end
def find_user
@user = User.find_by(email: new_agent_params[:email])
end
# TODO: move this to a builder and combine the save account user method into a builder
# ensure the account user association is also created in a single transaction
def create_user
return @user.send_confirmation_instructions if @user
@user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation))
end
def save_account_user
AccountUser.create!({
account_id: Current.account.id,
user_id: @user.id,
inviter_id: current_user.id
}.merge({
role: new_agent_params[:role],
availability: new_agent_params[:availability],
auto_offline: new_agent_params[:auto_offline]
}.compact))
end
def agent_params def agent_params
params.require(:agent).permit(:name, :email, :name, :role, :availability, :auto_offline) params.require(:agent).permit(:name, :email, :name, :role, :availability, :auto_offline)
end end
def new_agent_params def new_agent_params
# intial string ensures the password requirements are met
temp_password = "1!aA#{SecureRandom.alphanumeric(12)}"
params.require(:agent).permit(:email, :name, :role, :availability, :auto_offline) params.require(:agent).permit(:email, :name, :role, :availability, :auto_offline)
.merge!(password: temp_password, password_confirmation: temp_password, inviter: current_user)
end end
def agents def agents
@agents ||= Current.account.users.order_by_full_name.includes(:account_users, { avatar_attachment: [:blob] }) @agents ||= Current.account.users.order_by_full_name.includes(:account_users, { avatar_attachment: [:blob] })
end end
def validate_limit_for_bulk_create
limit_available = params[:emails].count <= available_agent_count
render_payment_required('Account limit exceeded. Please purchase more licenses') unless limit_available
end
def validate_limit def validate_limit
render_payment_required('Account limit exceeded. Please purchase more licenses') unless can_add_agent? render_payment_required('Account limit exceeded. Please purchase more licenses') if agents.count >= Current.account.usage_limits[:agents]
end
def available_agent_count
Current.account.usage_limits[:agents] - agents.count
end
def can_add_agent?
available_agent_count.positive?
end end
def delete_user_record(agent) def delete_user_record(agent)
@@ -2,7 +2,7 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
RESULTS_PER_PAGE = 15 RESULTS_PER_PAGE = 15
include DateRangeHelper include DateRangeHelper
before_action :fetch_notification, only: [:update, :destroy, :snooze, :unread] before_action :fetch_notification, only: [:update, :destroy, :snooze]
before_action :set_primary_actor, only: [:read_all] before_action :set_primary_actor, only: [:read_all]
before_action :set_current_page, only: [:index] before_action :set_current_page, only: [:index]
@@ -29,25 +29,11 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
render json: @notification render json: @notification
end end
def unread
@notification.update(read_at: nil)
render json: @notification
end
def destroy def destroy
@notification.destroy @notification.destroy
head :ok head :ok
end end
def destroy_all
if params[:type] == 'read'
::Notification::DeleteNotificationJob.perform_later(Current.user, type: :read)
else
::Notification::DeleteNotificationJob.perform_later(Current.user, type: :all)
end
head :ok
end
def unread_count def unread_count
@unread_count = notification_finder.unread_count @unread_count = notification_finder.unread_count
render json: @unread_count render json: @unread_count
+2 -15
View File
@@ -5,13 +5,11 @@ class Api::V1::AccountsController < Api::BaseController
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception, skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
only: [:create], raise: false only: [:create], raise: false
before_action :check_signup_enabled, only: [:create] before_action :check_signup_enabled, only: [:create]
before_action :ensure_account_name, only: [:create]
before_action :validate_captcha, only: [:create] before_action :validate_captcha, only: [:create]
before_action :fetch_account, except: [:create] before_action :fetch_account, except: [:create]
before_action :check_authorization, except: [:create] before_action :check_authorization, except: [:create]
rescue_from CustomExceptions::Account::InvalidEmail, rescue_from CustomExceptions::Account::InvalidEmail,
CustomExceptions::Account::InvalidParams,
CustomExceptions::Account::UserExists, CustomExceptions::Account::UserExists,
CustomExceptions::Account::UserErrors, CustomExceptions::Account::UserErrors,
with: :render_error_response with: :render_error_response
@@ -40,7 +38,7 @@ class Api::V1::AccountsController < Api::BaseController
def cache_keys def cache_keys
expires_in 10.seconds, public: false, stale_while_revalidate: 5.minutes expires_in 10.seconds, public: false, stale_while_revalidate: 5.minutes
render json: { cache_keys: cache_keys_for_account }, status: :ok render json: { cache_keys: get_cache_keys }, status: :ok
end end
def update def update
@@ -55,18 +53,7 @@ class Api::V1::AccountsController < Api::BaseController
private private
def ensure_account_name def get_cache_keys
# ensure that account_name and user_full_name is present
# this is becuase the account builder and the models validations are not triggered
# this change is to align the behaviour with the v2 accounts controller
# since these values are not required directly there
return if account_params[:account_name].present?
return if account_params[:user_full_name].present?
raise CustomExceptions::Account::InvalidParams.new({})
end
def cache_keys_for_account
{ {
label: fetch_value_for_key(params[:id], Label.name.underscore), label: fetch_value_for_key(params[:id], Label.name.underscore),
inbox: fetch_value_for_key(params[:id], Inbox.name.underscore), inbox: fetch_value_for_key(params[:id], Inbox.name.underscore),
@@ -31,11 +31,6 @@ class Api::V1::ProfilesController < Api::BaseController
head :ok head :ok
end end
def resend_confirmation
@user.send_confirmation_instructions unless @user.confirmed?
head :ok
end
private private
def set_user def set_user
@@ -1,48 +0,0 @@
class Api::V2::AccountsController < Api::BaseController
include AuthHelper
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
only: [:create], raise: false
before_action :check_signup_enabled, only: [:create]
before_action :validate_captcha, only: [:create]
before_action :fetch_account, except: [:create]
before_action :check_authorization, except: [:create]
rescue_from CustomExceptions::Account::InvalidEmail,
CustomExceptions::Account::UserExists,
CustomExceptions::Account::UserErrors,
with: :render_error_response
def create
@user, @account = AccountBuilder.new(
email: account_params[:email],
user_password: account_params[:password],
user: current_user
).perform
if @user
send_auth_headers(@user)
render 'api/v1/accounts/create', format: :json, locals: { resource: @user }
else
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
end
end
private
def fetch_account
@account = current_user.accounts.find(params[:id])
@current_account_user = @account.account_users.find_by(user_id: current_user.id)
end
def account_params
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
end
def check_signup_enabled
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
end
def validate_captcha
raise ActionController::InvalidAuthenticityToken, 'Invalid Captcha' unless ChatwootCaptcha.new(params[:h_captcha_client_response]).valid?
end
end
@@ -22,24 +22,19 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
i.value = value i.value = value
i.save! i.save!
end end
redirect_to super_admin_settings_path, notice: "App Configs - #{@config.titleize} updated successfully" # rubocop:disable Rails/I18nLocaleTexts
redirect_to super_admin_settings_path, notice: 'App Configs updated successfully'
# rubocop:enable Rails/I18nLocaleTexts
end end
private private
def set_config def set_config
@config = params[:config] || 'general' @config = params[:config]
end end
def allowed_configs def allowed_configs
@allowed_configs = case @config @allowed_configs = %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET]
when 'facebook'
%w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT]
when 'email'
['MAILER_INBOUND_EMAIL_DOMAIN']
else
%w[ENABLE_ACCOUNT_SIGNUP]
end
end end
end end
+1 -6
View File
@@ -26,7 +26,6 @@ class NotificationFinder
def set_up def set_up
find_all_notifications find_all_notifications
filter_by_read_status
filter_by_status filter_by_status
end end
@@ -38,15 +37,11 @@ class NotificationFinder
@notifications = @notifications.where('snoozed_until > ?', DateTime.now.utc) if params[:status] == 'snoozed' @notifications = @notifications.where('snoozed_until > ?', DateTime.now.utc) if params[:status] == 'snoozed'
end end
def filter_by_read_status
@notifications = @notifications.where.not(read_at: nil) if params[:type] == 'read'
end
def current_page def current_page
params[:page] || 1 params[:page] || 1
end end
def notifications def notifications
@notifications.page(current_page).per(RESULTS_PER_PAGE).order(last_activity_at: params[:sort_order] || :desc) @notifications.page(current_page).per(RESULTS_PER_PAGE).order(last_activity_at: :desc)
end end
end end
@@ -1,9 +0,0 @@
module SuperAdmin::AccountFeaturesHelper
def self.account_features
YAML.safe_load(Rails.root.join('config/features.yml').read).freeze
end
def self.account_premium_features
account_features.filter { |feature| feature['premium'] }.pluck('name')
end
end
+1 -4
View File
@@ -2,13 +2,12 @@
<div <div
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem" v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
id="app" id="app"
class="flex-grow-0 w-full h-full min-h-0 app-wrapper" class="app-wrapper h-full flex-grow-0 min-h-0 w-full"
:class="{ 'app-rtl--wrapper': isRTLView }" :class="{ 'app-rtl--wrapper': isRTLView }"
:dir="isRTLView ? 'rtl' : 'ltr'" :dir="isRTLView ? 'rtl' : 'ltr'"
> >
<update-banner :latest-chatwoot-version="latestChatwootVersion" /> <update-banner :latest-chatwoot-version="latestChatwootVersion" />
<template v-if="currentAccountId"> <template v-if="currentAccountId">
<pending-email-verification-banner />
<payment-pending-banner /> <payment-pending-banner />
<upgrade-banner /> <upgrade-banner />
</template> </template>
@@ -33,7 +32,6 @@ import NetworkNotification from './components/NetworkNotification.vue';
import UpdateBanner from './components/app/UpdateBanner.vue'; import UpdateBanner from './components/app/UpdateBanner.vue';
import UpgradeBanner from './components/app/UpgradeBanner.vue'; import UpgradeBanner from './components/app/UpgradeBanner.vue';
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue'; import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
import vueActionCable from './helper/actionCable'; import vueActionCable from './helper/actionCable';
import WootSnackbarBox from './components/SnackbarContainer.vue'; import WootSnackbarBox from './components/SnackbarContainer.vue';
import rtlMixin from 'shared/mixins/rtlMixin'; import rtlMixin from 'shared/mixins/rtlMixin';
@@ -54,7 +52,6 @@ export default {
PaymentPendingBanner, PaymentPendingBanner,
WootSnackbarBox, WootSnackbarBox,
UpgradeBanner, UpgradeBanner,
PendingEmailVerificationBanner,
}, },
mixins: [rtlMixin], mixins: [rtlMixin],
+2 -7
View File
@@ -29,12 +29,11 @@ export default {
return fetchPromise; return fetchPromise;
}, },
hasAuthCookie() { hasAuthCookie() {
return !!Cookies.get('cw_d_session_info'); return !!Cookies.getJSON('cw_d_session_info');
}, },
getAuthData() { getAuthData() {
if (this.hasAuthCookie()) { if (this.hasAuthCookie()) {
const savedAuthInfo = Cookies.get('cw_d_session_info'); return Cookies.getJSON('cw_d_session_info');
return JSON.parse(savedAuthInfo || '{}');
} }
return false; return false;
}, },
@@ -98,8 +97,4 @@ export default {
}, },
}); });
}, },
resendConfirmation() {
const urlData = endPoints('resendConfirmation');
return axios.post(urlData.url);
},
}; };
@@ -47,10 +47,6 @@ const endPoints = {
setActiveAccount: { setActiveAccount: {
url: '/api/v1/profile/set_active_account', url: '/api/v1/profile/set_active_account',
}, },
resendConfirmation: {
url: '/api/v1/profile/resend_confirmation',
},
}; };
export default page => { export default page => {
+2 -29
View File
@@ -6,15 +6,8 @@ class NotificationsAPI extends ApiClient {
super('notifications', { accountScoped: true }); super('notifications', { accountScoped: true });
} }
get({ page, status, type, sortOrder }) { get(page) {
return axios.get(this.url, { return axios.get(`${this.url}?page=${page}`);
params: {
page,
status,
type,
sort_order: sortOrder,
},
});
} }
getNotifications(contactId) { getNotifications(contactId) {
@@ -32,29 +25,9 @@ class NotificationsAPI extends ApiClient {
}); });
} }
unRead(id) {
return axios.post(`${this.url}/${id}/unread`);
}
readAll() { readAll() {
return axios.post(`${this.url}/read_all`); return axios.post(`${this.url}/read_all`);
} }
delete(id) {
return axios.delete(`${this.url}/${id}`);
}
deleteAll({ type = 'all' }) {
return axios.post(`${this.url}/destroy_all`, {
type,
});
}
snooze({ id, snoozedUntil = null }) {
return axios.post(`${this.url}/${id}/snooze`, {
snoozed_until: snoozedUntil,
});
}
} }
export default new NotificationsAPI(); export default new NotificationsAPI();
-11
View File
@@ -1,11 +0,0 @@
// /* global axios */
import ApiClient from './ApiClient';
class SlaAPI extends ApiClient {
constructor() {
super('sla_policies', { accountScoped: true });
}
}
export default new SlaAPI();
@@ -28,20 +28,10 @@ describe('#NotificationAPI', () => {
}); });
it('#get', () => { it('#get', () => {
notificationsAPI.get({ notificationsAPI.get(1);
page: 1, expect(axiosMock.get).toHaveBeenCalledWith(
status: 'read', '/api/v1/notifications?page=1'
type: 'Conversation', );
sortOrder: 'desc',
});
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/notifications', {
params: {
page: 1,
status: 'read',
type: 'Conversation',
sort_order: 'desc',
},
});
}); });
it('#getNotifications', () => { it('#getNotifications', () => {
@@ -75,30 +65,5 @@ describe('#NotificationAPI', () => {
'/api/v1/notifications/read_all' '/api/v1/notifications/read_all'
); );
}); });
it('#snooze', () => {
notificationsAPI.snooze({ id: 1, snoozedUntil: 12332211 });
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/notifications/1/snooze',
{
snoozed_until: 12332211,
}
);
});
it('#delete', () => {
notificationsAPI.delete(1);
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/notifications/1');
});
it('#deleteAll', () => {
notificationsAPI.deleteAll({ type: 'all' });
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/notifications/destroy_all',
{
type: 'all',
}
);
});
}); });
}); });
@@ -1,4 +1,4 @@
@import '~vue2-datepicker/scss/index'; @import 'vue2-datepicker/scss/index';
.date-picker { .date-picker {
&.no-margin { &.no-margin {
@@ -1,4 +1,4 @@
@import '~dashboard/assets/scss/variables'; @import 'dashboard/assets/scss/variables';
.formulate-input { .formulate-input {
.formulate-input-errors { .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; @apply bg-slate-25 dark:bg-slate-800;
} }
.flex-center {
@include flex-align(center, middle);
display: flex;
}
.bottom-space-fix { .bottom-space-fix {
margin-bottom: auto; margin-bottom: auto;
} }
@@ -1,5 +1,5 @@
@import '~dashboard/assets/scss/variables'; @import 'dashboard/assets/scss/variables';
@import '~widget/assets/scss/mixins'; @import 'widget/assets/scss/mixins';
$spinner-before-border-color: rgba(255, 255, 255, 0.7); $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-function: cubic-bezier(0.37, 0, 0.63, 1) !default;
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-function !default; $swift-ease-out: all $swift-ease-out-duration $swift-ease-out-function !default;
// Ionicons
$ionicons-font-path: '~ionicons/fonts';
// Transitions // Transitions
$transition-ease-in: all 0.250s ease-in; $transition-ease-in: all 0.250s ease-in;
@@ -15,36 +15,13 @@
@import 'variables'; @import 'variables';
@import 'mixins'; @import 'mixins';
@import 'foundation-settings';
@import 'helper-classes'; @import 'helper-classes';
@import 'formulate'; @import 'formulate';
@import 'date-picker'; @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 'typography';
@import 'layout'; @import 'layout';
@import 'animations'; @import 'animations';
@import 'foundation-custom';
@import 'rtl'; @import 'rtl';
@import 'widgets/buttons'; @import 'widgets/buttons';
@@ -52,15 +29,11 @@
@import 'widgets/conversation-card'; @import 'widgets/conversation-card';
@import 'widgets/conversation-view'; @import 'widgets/conversation-view';
@import 'widgets/forms'; @import 'widgets/forms';
@import 'widgets/login';
@import 'widgets/modal'; @import 'widgets/modal';
@import 'widgets/reply-box'; @import 'widgets/reply-box';
@import 'widgets/report'; @import 'widgets/report';
@import 'widgets/search-box';
@import 'widgets/sidemenu';
@import 'widgets/snackbar'; @import 'widgets/snackbar';
@import 'widgets/states'; @import 'widgets/states';
@import 'widgets/status-bar';
@import 'widgets/tabs'; @import 'widgets/tabs';
@import 'widgets/woot-tables'; @import 'widgets/woot-tables';
@@ -69,7 +42,6 @@
@import 'plugins/multiselect'; @import 'plugins/multiselect';
@import 'plugins/dropdown'; @import 'plugins/dropdown';
@import '~shared/assets/stylesheets/ionicons';
@import 'utility-helpers'; @import 'utility-helpers';
.tooltip { .tooltip {
@@ -10,21 +10,14 @@
@import 'variables'; @import 'variables';
@import 'vue-multiselect/dist/vue-multiselect.min.css'; @import 'vue-multiselect/dist/vue-multiselect.min.css';
@import '~shared/assets/stylesheets/ionicons';
@import 'mixins'; @import 'mixins';
@import 'foundation-settings';
@import 'helper-classes'; @import 'helper-classes';
@import 'foundation-sites/scss/foundation';
@include foundation-prototype-spacing;
@include foundation-everything($flex: true);
@import 'typography'; @import 'typography';
@import 'layout'; @import 'layout';
@import 'animations'; @import 'animations';
@import 'foundation-custom';
@import 'widgets/buttons'; @import 'widgets/buttons';
@import 'widgets/forms'; @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 { .modal-mask {
// @include flex; @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;
// @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;
} }
.page-top-bar { .page-top-bar {
@@ -45,8 +43,6 @@
} }
.modal-footer { .modal-footer {
// @include flex;
// @include flex-align($x: flex-end, $y: middle);
@apply flex justify-end items-center py-2 px-0 gap-2; @apply flex justify-end items-center py-2 px-0 gap-2;
&.justify-content-end { &.justify-content-end {
@@ -8,7 +8,7 @@
.reply-box__top { .reply-box__top {
.icon { .icon {
color: $medium-gray; color: var(--slate-500);
cursor: pointer; cursor: pointer;
font-size: $font-size-medium; font-size: $font-size-medium;
margin-right: $space-small; margin-right: $space-small;
@@ -34,7 +34,7 @@
resize: none; resize: none;
} }
> textarea { >textarea {
@include ghost-input(); @include ghost-input();
background: transparent; background: transparent;
margin: 0; margin: 0;
@@ -52,7 +52,7 @@
.reply-box__top { .reply-box__top {
@apply bg-yellow-100 dark:bg-yellow-800; @apply bg-yellow-100 dark:bg-yellow-800;
> input { >input {
@apply bg-yellow-100 dark:bg-yellow-800; @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
.bottom-nav { .bottom-nav {
@include flex; @include flex;
@include space-between-column; @include space-between-column;
@include border-normal-top; @include border-normal-top;
flex-direction: column; flex-direction: column;
padding: var(--space-one) var(--space-normal) var(--space-one) padding: var(--space-one) var(--space-normal) var(--space-one) var(--space-one);
var(--space-one);
position: relative; position: relative;
&:hover { &:hover {
@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="conversations-list-wrap flex-basis-clamp flex-shrink-0 overflow-hidden flex flex-col border-r rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50" class="conversations-list-wrap flex-basis-clamp flex-shrink-0 flex-basis-custom overflow-hidden flex flex-col border-r rtl:border-r-0 rtl:border-l border-slate-50 dark:border-slate-800/50"
:class="{ :class="{
hide: !showConversationList, hide: !showConversationList,
'list--full-width': isOnExpandedLayout, 'list--full-width': isOnExpandedLayout,
@@ -13,7 +13,7 @@
<div class="flex items-center justify-between w-full"> <div class="flex items-center justify-between w-full">
<span <span
class="attribute-name" class="attribute-name"
:class="{ error: $v.editedValue.$error }" :class="{ error: v$.editedValue.$error }"
> >
{{ label }} {{ label }}
</span> </span>
@@ -39,8 +39,8 @@
:type="inputType" :type="inputType"
class="input-group-field" class="input-group-field"
autofocus="true" autofocus="true"
:class="{ error: $v.editedValue.$error }" :class="{ error: v$.editedValue.$error }"
@blur="$v.editedValue.$touch" @blur="v$.editedValue.$touch"
@keyup.enter="onUpdate" @keyup.enter="onUpdate"
/> />
<div class="input-group-button"> <div class="input-group-button">
@@ -122,12 +122,13 @@
<script> <script>
import { format, parseISO } from 'date-fns'; 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 { BUS_EVENTS } from 'shared/constants/busEvents';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue'; import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import { isValidURL } from '../helper/URLHelper'; import { isValidURL } from '../helper/URLHelper';
import customAttributeMixin from '../mixins/customAttributeMixin'; import customAttributeMixin from '../mixins/customAttributeMixin';
const DATE_FORMAT = 'yyyy-MM-dd'; const DATE_FORMAT = 'yyyy-MM-dd';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
components: { components: {
@@ -149,6 +150,9 @@ export default {
attributeKey: { type: String, required: true }, attributeKey: { type: String, required: true },
contactId: { type: Number, default: null }, contactId: { type: Number, default: null },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
isEditing: false, isEditing: false,
@@ -206,13 +210,13 @@ export default {
return this.isAttributeTypeLink ? 'url' : this.attributeType; return this.isAttributeTypeLink ? 'url' : this.attributeType;
}, },
shouldShowErrorMessage() { shouldShowErrorMessage() {
return this.$v.editedValue.$error; return this.v$.editedValue.$error;
}, },
errorMessage() { errorMessage() {
if (this.$v.editedValue.url) { if (this.v$.editedValue.url) {
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_URL'); return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_URL');
} }
if (!this.$v.editedValue.regexValidation) { if (!this.v$.editedValue.regexValidation) {
return this.regexCue return this.regexCue
? this.regexCue ? this.regexCue
: this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_INPUT'); : this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_INPUT');
@@ -280,8 +284,8 @@ export default {
this.attributeType === 'date' this.attributeType === 'date'
? parseISO(this.editedValue) ? parseISO(this.editedValue)
: this.editedValue; : this.editedValue;
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) { if (this.v$.$invalid) {
return; return;
} }
this.isEditing = false; this.isEditing = false;
@@ -302,11 +306,14 @@ export default {
.checkbox-wrap { .checkbox-wrap {
@apply flex items-center; @apply flex items-center;
} }
.checkbox { .checkbox {
@apply my-0 mr-2 ml-0; @apply my-0 mr-2 ml-0;
} }
.attribute-name { .attribute-name {
@apply w-full text-slate-800 dark:text-slate-100; @apply w-full text-slate-800 dark:text-slate-100;
&.error { &.error {
@apply text-red-400 dark:text-red-500; @apply text-red-400 dark:text-red-500;
} }
@@ -323,6 +330,7 @@ export default {
.value { .value {
@apply bg-slate-50 dark:bg-slate-700 mb-0; @apply bg-slate-50 dark:bg-slate-700 mb-0;
} }
.edit-button { .edit-button {
@apply block; @apply block;
} }
@@ -332,10 +340,12 @@ export default {
::v-deep { ::v-deep {
.selector-wrap { .selector-wrap {
@apply m-0 top-1; @apply m-0 top-1;
.selector-name { .selector-name {
@apply ml-0; @apply ml-0;
} }
} }
.name { .name {
@apply ml-0; @apply ml-0;
} }
@@ -1,43 +0,0 @@
<template>
<banner
v-if="shouldShowBanner"
color-scheme="alert"
:banner-message="bannerMessage"
:action-button-label="actionButtonMessage"
action-button-icon="mail"
has-action-button
@click="resendVerificationEmail"
/>
</template>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import { mapGetters } from 'vuex';
import accountMixin from 'dashboard/mixins/account';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: { Banner },
mixins: [accountMixin, alertMixin],
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
}),
bannerMessage() {
return this.$t('APP_GLOBAL.EMAIL_VERIFICATION_PENDING');
},
actionButtonMessage() {
return this.$t('APP_GLOBAL.RESEND_VERIFICATION_MAIL');
},
shouldShowBanner() {
return !this.currentUser.confirmed;
},
},
methods: {
resendVerificationEmail() {
this.$store.dispatch('resendConfirmation');
this.showAlert(this.$t('APP_GLOBAL.EMAIL_VERIFICATION_SENT'));
},
},
};
</script>
+15 -15
View File
@@ -1,29 +1,29 @@
/* eslint no-plusplus: 0 */ /* eslint no-plusplus: 0 */
import AvatarUploader from './widgets/forms/AvatarUploader.vue'; import AvatarUploader from './widgets/forms/AvatarUploader.vue';
import Bar from './widgets/chart/BarChart'; import Bar from './widgets/chart/BarChart';
import Button from './ui/WootButton'; import Button from './ui/WootButton.vue';
import Code from './Code'; import Code from './Code.vue';
import ColorPicker from './widgets/ColorPicker'; import ColorPicker from './widgets/ColorPicker.vue';
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue'; import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
import ConfirmModal from './widgets/modal/ConfirmationModal.vue'; import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
import ContextMenu from './ui/ContextMenu.vue'; import ContextMenu from './ui/ContextMenu.vue';
import DeleteModal from './widgets/modal/DeleteModal.vue'; import DeleteModal from './widgets/modal/DeleteModal.vue';
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem'; import DropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu'; import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import FeatureToggle from './widgets/FeatureToggle'; import FeatureToggle from './widgets/FeatureToggle.vue';
import HorizontalBar from './widgets/chart/HorizontalBarChart'; import HorizontalBar from './widgets/chart/HorizontalBarChart';
import Input from './widgets/forms/Input.vue'; import Input from './widgets/forms/Input.vue';
import PhoneInput from './widgets/forms/PhoneInput.vue'; import PhoneInput from './widgets/forms/PhoneInput.vue';
import Label from './ui/Label'; import Label from './ui/Label.vue';
import LoadingState from './widgets/LoadingState'; import LoadingState from './widgets/LoadingState.vue';
import Modal from './Modal'; import Modal from './Modal.vue';
import ModalHeader from './ModalHeader'; import ModalHeader from './ModalHeader.vue';
import ReportStatsCard from './widgets/ReportStatsCard'; import ReportStatsCard from './widgets/ReportStatsCard.vue';
import SidemenuIcon from './SidemenuIcon'; import SidemenuIcon from './SidemenuIcon.vue';
import Spinner from 'shared/components/Spinner'; import Spinner from 'shared/components/Spinner.vue';
import SubmitButton from './buttons/FormSubmitButton'; import SubmitButton from './buttons/FormSubmitButton.vue';
import Tabs from './ui/Tabs/Tabs'; 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'; import Thumbnail from './widgets/Thumbnail.vue';
const WootUIKit = { const WootUIKit = {
@@ -39,7 +39,6 @@ const settings = accountId => ({
'settings_teams_finish', 'settings_teams_finish',
'settings_teams_list', 'settings_teams_list',
'settings_teams_new', 'settings_teams_new',
'sla_list',
], ],
menuItems: [ menuItems: [
{ {
@@ -159,15 +158,6 @@ const settings = accountId => ({
featureFlag: FEATURE_FLAGS.AUDIT_LOGS, featureFlag: FEATURE_FLAGS.AUDIT_LOGS,
beta: true, beta: true,
}, },
{
icon: 'key',
label: 'SLA',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/sla/list`),
toStateName: 'sla_list',
featureFlag: FEATURE_FLAGS.SLA,
beta: true,
},
], ],
}); });
@@ -19,13 +19,13 @@
<form class="flex flex-col w-full" @submit.prevent="addAccount"> <form class="flex flex-col w-full" @submit.prevent="addAccount">
<div class="w-full"> <div class="w-full">
<label :class="{ error: $v.accountName.$error }"> <label :class="{ error: v$.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }} {{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input <input
v-model.trim="accountName" v-model.trim="accountName"
type="text" type="text"
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')" :placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
@input="$v.accountName.$touch" @input="v$.accountName.$touch"
/> />
</label> </label>
</div> </div>
@@ -33,8 +33,8 @@
<div class="w-full"> <div class="w-full">
<woot-submit-button <woot-submit-button
:disabled=" :disabled="
$v.accountName.$invalid || v$.accountName.$invalid ||
$v.accountName.$invalid || v$.accountName.$invalid ||
uiFlags.isCreating uiFlags.isCreating
" "
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')" :button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
@@ -49,9 +49,10 @@
</template> </template>
<script> <script>
import { required, minLength } from 'vuelidate/lib/validators'; import { required, minLength } from '@vuelidate/validators';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
mixins: [alertMixin], mixins: [alertMixin],
@@ -65,6 +66,9 @@ export default {
default: true, default: true,
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
accountName: '', accountName: '',
@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="flex items-center justify-center h-12 gap-4 px-4 py-3 text-xs text-white banner dark:text-white" class="banner flex items-center h-12 gap-4 text-white dark:text-white text-xs py-3 px-4 justify-center"
:class="bannerClasses" :class="bannerClasses"
> >
<span class="banner-message"> <span class="banner-message">
@@ -18,7 +18,7 @@
<woot-button <woot-button
v-if="hasActionButton" v-if="hasActionButton"
size="tiny" size="tiny"
:icon="actionButtonIcon" icon="arrow-right"
:variant="actionButtonVariant" :variant="actionButtonVariant"
color-scheme="primary" color-scheme="primary"
class-names="banner-action__button" class-names="banner-action__button"
@@ -67,10 +67,6 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
actionButtonIcon: {
type: String,
default: 'arrow-right',
},
colorScheme: { colorScheme: {
type: String, type: String,
default: '', default: '',
@@ -1,4 +1,6 @@
export default { import { defineComponent } from 'vue';
export default defineComponent({
name: 'WootTabs', name: 'WootTabs',
props: { props: {
index: { index: {
@@ -73,19 +75,25 @@ export default {
}); });
const leftButton = this.createScrollButton(createElement, 'left'); const leftButton = this.createScrollButton(createElement, 'left');
const rightButton = this.createScrollButton(createElement, 'right'); const rightButton = this.createScrollButton(createElement, 'right');
return ( return createElement(
<div 'div',
class={{ {
class: {
'tabs--container--with-border': this.border, 'tabs--container--with-border': this.border,
'tabs--container': true, 'tabs--container': true,
}} },
> },
{leftButton} [
<ul class={{ tabs: true, 'tabs--with-scroll': this.hasScroll }}> leftButton,
{Tabs} createElement(
</ul> 'ul',
{rightButton} {
</div> class: { tabs: true, 'tabs--with-scroll': this.hasScroll },
},
Tabs
),
rightButton,
]
); );
}, },
}; });
@@ -12,11 +12,11 @@
<woot-input <woot-input
v-model="value" v-model="value"
type="text" type="text"
:class="{ error: $v.value.$error }" :class="{ error: v$.value.$error }"
:placeholder=" :placeholder="
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER') $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
" "
@blur="$v.value.$touch" @blur="v$.value.$touch"
/> />
</div> </div>
<div class="flex flex-row justify-between gap-2 py-2 px-0 w-full"> <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"> <woot-button variant="clear" @click.prevent="onDismiss">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }} {{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
</woot-button> </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') }} {{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
</woot-button> </woot-button>
</div> </div>
@@ -37,15 +37,19 @@
</template> </template>
<script> <script>
import { required } from 'vuelidate/lib/validators'; import { required } from '@vuelidate/validators';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import aiMixin from 'dashboard/mixins/aiMixin'; import aiMixin from 'dashboard/mixins/aiMixin';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
mixins: [aiMixin, alertMixin, uiSettingsMixin], mixins: [aiMixin, alertMixin, uiSettingsMixin],
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
value: '', value: '',
@@ -53,8 +53,8 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~dashboard/assets/scss/variables'; @import 'dashboard/assets/scss/variables';
@import '~dashboard/assets/scss/mixins'; @import 'dashboard/assets/scss/mixins';
.colorpicker { .colorpicker {
position: relative; position: relative;
@@ -705,10 +705,11 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import '~@chatwoot/prosemirror-schema/src/styles/base.scss'; @import '@chatwoot/prosemirror-schema/src/styles/base.scss';
.ProseMirror-menubar-wrapper { .ProseMirror-menubar-wrapper {
@apply flex flex-col; @apply flex flex-col;
.ProseMirror-menubar { .ProseMirror-menubar {
min-height: var(--space-two) !important; 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; @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; @apply bg-slate-75 dark:bg-slate-800;
} }
} }
> .ProseMirror { > .ProseMirror {
@apply p-0 break-words text-slate-800 dark:text-slate-100; @apply p-0 break-words text-slate-800 dark:text-slate-100;
@@ -41,7 +41,7 @@
:show-query-operator="i !== appliedFilters.length - 1" :show-query-operator="i !== appliedFilters.length - 1"
:show-user-input="showUserInput(appliedFilters[i].filter_operator)" :show-user-input="showUserInput(appliedFilters[i].filter_operator)"
:grouped-filters="true" :grouped-filters="true"
:v="$v.appliedFilters.$each[i]" :v="v$.appliedFilters.$each[i]"
@resetFilter="resetFilter(i, appliedFilters[i])" @resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)" @removeFilter="removeFilter(i)"
/> />
@@ -80,7 +80,7 @@
<script> <script>
import alertMixin from 'shared/mixins/alertMixin'; 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 FilterInputBox from '../FilterInput/Index.vue';
import languages from './advancedFilterItems/languages'; import languages from './advancedFilterItems/languages';
import countries from 'shared/constants/countries.js'; import countries from 'shared/constants/countries.js';
@@ -89,6 +89,7 @@ import { filterAttributeGroups } from './advancedFilterItems';
import filterMixin from 'shared/mixins/filterMixin'; import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'; import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events'; import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
components: { components: {
@@ -117,6 +118,9 @@ export default {
default: false, default: false,
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
validations: { validations: {
appliedFilters: { appliedFilters: {
required, required,
@@ -347,8 +351,8 @@ export default {
} }
}, },
submitFilterQuery() { submitFilterQuery() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) return; if (this.v$.$invalid) return;
this.$store.dispatch( this.$store.dispatch(
'setConversationFilters', 'setConversationFilters',
JSON.parse(JSON.stringify(this.appliedFilters)) JSON.parse(JSON.stringify(this.appliedFilters))
@@ -6,7 +6,6 @@
<conversation-header <conversation-header
v-if="currentChat.id" v-if="currentChat.id"
:chat="currentChat" :chat="currentChat"
:is-inbox-view="isInboxView"
:is-contact-panel-open="isContactPanelOpen" :is-contact-panel-open="isContactPanelOpen"
:show-back-button="isOnExpandedLayout" :show-back-button="isOnExpandedLayout"
@contact-panel-toggle="onToggleContactPanel" @contact-panel-toggle="onToggleContactPanel"
@@ -31,7 +30,6 @@
<messages-view <messages-view
v-if="currentChat.id" v-if="currentChat.id"
:inbox-id="inboxId" :inbox-id="inboxId"
:is-inbox-view="isInboxView"
:is-contact-panel-open="isContactPanelOpen" :is-contact-panel-open="isContactPanelOpen"
@contact-panel-toggle="onToggleContactPanel" @contact-panel-toggle="onToggleContactPanel"
/> />
@@ -82,10 +80,6 @@ export default {
default: '', default: '',
required: false, required: false,
}, },
isInboxView: {
type: Boolean,
default: false,
},
isContactPanelOpen: { isContactPanelOpen: {
type: Boolean, type: Boolean,
default: true, default: true,
@@ -66,7 +66,7 @@
</div> </div>
</div> </div>
<div <div
class="header-actions-wrap items-center flex flex-row flex-grow justify-end mt-3 lg:mt-0 rtl:relative rtl:left-6" class="header-actions-wrap items-center flex flex-row flex-grow justify-end mt-3 lg:mt-0"
:class="{ 'justify-end': isContactPanelOpen }" :class="{ 'justify-end': isContactPanelOpen }"
> >
<more-actions :conversation-id="currentChat.id" /> <more-actions :conversation-id="currentChat.id" />
@@ -86,8 +86,7 @@ import MoreActions from './MoreActions.vue';
import Thumbnail from '../Thumbnail.vue'; import Thumbnail from '../Thumbnail.vue';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
import { conversationListPageURL } from 'dashboard/helper/URLHelper'; import { conversationListPageURL } from 'dashboard/helper/URLHelper';
import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers'; import { conversationReopenTime } from 'dashboard/helper/snoozeHelpers';
import { frontendURL } from 'dashboard/helper/URLHelper';
export default { export default {
components: { components: {
@@ -110,10 +109,6 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isInboxView: {
type: Boolean,
default: false,
},
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
@@ -128,9 +123,6 @@ export default {
params: { accountId, inbox_id: inboxId, label, teamId }, params: { accountId, inbox_id: inboxId, label, teamId },
name, name,
} = this.$route; } = this.$route;
if (this.isInboxView) {
return frontendURL(`accounts/${accountId}/inbox`);
}
return conversationListPageURL({ return conversationListPageURL({
accountId, accountId,
inboxId, inboxId,
@@ -158,7 +150,7 @@ export default {
if (snoozedUntil) { if (snoozedUntil) {
return `${this.$t( return `${this.$t(
'CONVERSATION.HEADER.SNOOZED_UNTIL' 'CONVERSATION.HEADER.SNOOZED_UNTIL'
)} ${snoozedReopenTime(snoozedUntil)}`; )} ${conversationReopenTime(snoozedUntil)}`;
} }
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY'); return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
}, },
@@ -45,14 +45,14 @@
}}</label> }}</label>
</div> </div>
<div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1"> <div v-if="sentToOtherEmailAddress" class="w-[50%] mt-1">
<label :class="{ error: $v.email.$error }"> <label :class="{ error: v$.email.$error }">
<input <input
v-model.trim="email" v-model.trim="email"
type="text" type="text"
:placeholder="$t('EMAIL_TRANSCRIPT.FORM.EMAIL.PLACEHOLDER')" :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') }} {{ $t('EMAIL_TRANSCRIPT.FORM.EMAIL.ERROR') }}
</span> </span>
</label> </label>
@@ -73,8 +73,10 @@
</template> </template>
<script> <script>
import { required, minLength, email } from 'vuelidate/lib/validators'; import { required, minLength, email } from '@vuelidate/validators';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
mixins: [alertMixin], mixins: [alertMixin],
props: { props: {
@@ -87,6 +89,9 @@ export default {
default: () => ({}), default: () => ({}),
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
email: '', email: '',
@@ -108,7 +113,7 @@ export default {
isFormValid() { isFormValid() {
if (this.selectedType) { if (this.selectedType) {
if (this.sentToOtherEmailAddress) { if (this.sentToOtherEmailAddress) {
return !!this.email && !this.$v.email.$error; return !!this.email && !this.v$.email.$error;
} }
return true; return true;
} }
@@ -12,10 +12,7 @@
variant="smooth" variant="smooth"
size="tiny" size="tiny"
color-scheme="secondary" color-scheme="secondary"
class="rounded-bl-calc rtl:rotate-180 rounded-tl-calc fixed z-10 bg-white dark:bg-slate-700 border-slate-50 dark:border-slate-600 border-solid border border-r-0 box-border" class="rounded-bl-calc rtl:rotate-180 rounded-tl-calc fixed top-[9.5rem] md:top-[6.25rem] z-10 bg-white dark:bg-slate-700 border-slate-50 dark:border-slate-600 border-solid border border-r-0 box-border"
:class="
isInboxView ? 'top-52 md:top-40' : 'top-[9.5rem] md:top-[6.25rem]'
"
:icon="isRightOrLeftIcon" :icon="isRightOrLeftIcon"
@click="onToggleContactPanel" @click="onToggleContactPanel"
/> />
@@ -145,10 +142,6 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isInboxView: {
type: Boolean,
default: false,
},
}, },
data() { data() {
@@ -164,7 +164,7 @@ import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel
import ReplyEmailHead from './ReplyEmailHead.vue'; import ReplyEmailHead from './ReplyEmailHead.vue';
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue'; import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
import ArticleSearchPopover from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.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 Banner from 'dashboard/components/ui/Banner.vue';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants'; import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue'; import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
@@ -198,7 +198,7 @@ import {
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage'; import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { LocalStorage } from 'shared/helpers/localStorage'; import { LocalStorage } from 'shared/helpers/localStorage';
const EmojiInput = () => import('shared/components/emoji/EmojiInput'); const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
export default { export default {
components: { components: {
@@ -1208,6 +1208,7 @@ export default {
@apply bg-yellow-50 dark:bg-yellow-200; @apply bg-yellow-50 dark:bg-yellow-200;
} }
} }
.send-button { .send-button {
@apply mb-0; @apply mb-0;
} }
@@ -1228,6 +1229,7 @@ export default {
.emoji-dialog--rtl { .emoji-dialog--rtl {
@apply left-[unset] -right-80; @apply left-[unset] -right-80;
&::before { &::before {
transform: rotate(90deg); transform: rotate(90deg);
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08)); filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
@@ -1,15 +1,15 @@
<template> <template>
<div> <div>
<div v-if="toEmails"> <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"> <label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }}
</label> </label>
<div class="input-group-field"> <div class="input-group-field">
<woot-input <woot-input
v-model.trim="$v.toEmailsVal.$model" v-model.trim="v$.toEmailsVal.$model"
type="text" type="text"
:class="{ error: $v.toEmailsVal.$error }" :class="{ error: v$.toEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')" :placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur" @blur="onBlur"
/> />
@@ -17,15 +17,15 @@
</div> </div>
</div> </div>
<div class="input-group-wrap"> <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"> <label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }}
</label> </label>
<div class="input-group-field"> <div class="input-group-field">
<woot-input <woot-input
v-model.trim="$v.ccEmailsVal.$model" v-model.trim="v$.ccEmailsVal.$model"
type="text" type="text"
:class="{ error: $v.ccEmailsVal.$error }" :class="{ error: v$.ccEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')" :placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur" @blur="onBlur"
/> />
@@ -39,20 +39,20 @@
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.ADD_BCC') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.ADD_BCC') }}
</woot-button> </woot-button>
</div> </div>
<span v-if="$v.ccEmailsVal.$error" class="message"> <span v-if="v$.ccEmailsVal.$error" class="message">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.ERROR') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.ERROR') }}
</span> </span>
</div> </div>
<div v-if="showBcc" class="input-group-wrap"> <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"> <label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }}
</label> </label>
<div class="input-group-field"> <div class="input-group-field">
<woot-input <woot-input
v-model.trim="$v.bccEmailsVal.$model" v-model.trim="v$.bccEmailsVal.$model"
type="text" type="text"
:class="{ error: $v.bccEmailsVal.$error }" :class="{ error: v$.bccEmailsVal.$error }"
:placeholder=" :placeholder="
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER') $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')
" "
@@ -60,7 +60,7 @@
/> />
</div> </div>
</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') }} {{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.ERROR') }}
</span> </span>
</div> </div>
@@ -137,7 +137,7 @@ export default {
this.showBcc = true; this.showBcc = true;
}, },
onBlur() { onBlur() {
this.$v.$touch(); this.v$.$touch();
this.$emit('update:bccEmails', this.bccEmailsVal); this.$emit('update:bccEmails', this.bccEmailsVal);
this.$emit('update:ccEmails', this.ccEmailsVal); this.$emit('update:ccEmails', this.ccEmailsVal);
this.$emit('update:toEmails', this.toEmailsVal); this.$emit('update:toEmails', this.toEmailsVal);
@@ -149,12 +149,14 @@ export default {
.input-group-wrap .message { .input-group-wrap .message {
@apply text-sm text-red-500 dark:text-red-500; @apply text-sm text-red-500 dark:text-red-500;
} }
.input-group { .input-group {
@apply border-b border-solid border-slate-75 dark:border-slate-700 my-1; @apply border-b border-solid border-slate-75 dark:border-slate-700 my-1;
.input-group-label { .input-group-label {
@apply border-transparent bg-transparent text-xs font-semibold pl-0; @apply border-transparent bg-transparent text-xs font-semibold pl-0;
} }
.input-group-field::v-deep input { .input-group-field::v-deep input {
@apply mb-0 border-transparent; @apply mb-0 border-transparent;
} }
@@ -162,6 +164,7 @@ export default {
.input-group.error { .input-group.error {
@apply border-b-red-500 dark:border-b-red-500; @apply border-b-red-500 dark:border-b-red-500;
.input-group-label { .input-group-label {
@apply text-red-500 dark:text-red-500; @apply text-red-500 dark:text-red-500;
} }
@@ -45,7 +45,9 @@ const allKeysRequired = value => {
const keys = Object.keys(value); const keys = Object.keys(value);
return keys.every(key => value[key]); return keys.every(key => value[key]);
}; };
import { requiredIf } from 'vuelidate/lib/validators'; import { useVuelidate } from '@vuelidate/core';
import { requiredIf } from '@vuelidate/validators';
export default { export default {
props: { props: {
template: { template: {
@@ -53,6 +55,9 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
validations: { validations: {
processedParams: { processedParams: {
requiredIfKeysPresent: requiredIf('variables'), requiredIfKeysPresent: requiredIf('variables'),
@@ -149,9 +154,11 @@ footer {
@apply ml-2.5; @apply ml-2.5;
} }
} }
.error { .error {
@apply bg-red-100 dark:bg-red-100 rounded-md text-red-800 dark:text-red-800 p-2.5 text-center; @apply bg-red-100 dark:bg-red-100 rounded-md text-red-800 dark:text-red-800 p-2.5 text-center;
} }
.template-input { .template-input {
@apply bg-slate-25 dark:bg-slate-900 text-slate-700 dark:text-slate-100; @apply bg-slate-25 dark:bg-slate-900 text-slate-700 dark:text-slate-100;
} }
@@ -46,7 +46,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~dashboard/assets/scss/variables'; @import 'dashboard/assets/scss/variables';
.file { .file {
display: flex; display: flex;
@@ -1,4 +1,4 @@
import emailValidator from 'vuelidate/lib/validators/email'; import { email as emailValidator } from '@vuelidate/validators';
export const validEmailsByComma = value => { export const validEmailsByComma = value => {
if (!value.length) return true; if (!value.length) return true;
@@ -6,12 +6,12 @@
<woot-input <woot-input
v-model="value" v-model="value"
type="text" type="text"
:class="{ error: $v.value.$error }" :class="{ error: v$.value.$error }"
:placeholder="confirmPlaceHolderText" :placeholder="confirmPlaceHolderText"
@blur="$v.value.$touch" @blur="v$.value.$touch"
/> />
<div class="button-wrapper"> <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 }} {{ confirmText }}
</woot-button> </woot-button>
<woot-button class="clear" @click.prevent="closeModal"> <woot-button class="clear" @click.prevent="closeModal">
@@ -23,14 +23,14 @@
</template> </template>
<script> <script>
import { required } from 'vuelidate/lib/validators'; import { required } from '@vuelidate/validators';
import Modal from '../../Modal.vue'; import Modal from '../../Modal.vue';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
components: { components: {
Modal, Modal,
}, },
props: { props: {
show: { show: {
type: Boolean, type: Boolean,
@@ -61,6 +61,9 @@ export default {
default: '', default: '',
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
value: '', value: '',
@@ -46,18 +46,5 @@ export default {
}, },
EXAMPLE_URL: 'https://example.com', EXAMPLE_URL: 'https://example.com',
EXAMPLE_WEBHOOK_URL: 'https://example/api/webhook', EXAMPLE_WEBHOOK_URL: 'https://example/api/webhook',
INBOX_SORT_BY: {
NEWEST: 'desc',
OLDEST: 'asc',
},
INBOX_DISPLAY_BY: {
SNOOZED: 'snoozed',
READ: 'read',
},
INBOX_FILTER_TYPE: {
STATUS: 'status',
TYPE: 'type',
SORT_ORDER: 'sort_order',
},
}; };
export const DEFAULT_REDIRECT_URL = '/app/'; export const DEFAULT_REDIRECT_URL = '/app/';
-1
View File
@@ -17,5 +17,4 @@ export const FEATURE_FLAGS = {
VOICE_RECORDER: 'voice_recorder', VOICE_RECORDER: 'voice_recorder',
AUDIT_LOGS: 'audit_logs', AUDIT_LOGS: 'audit_logs',
INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply', INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply',
INBOX_VIEW: 'inbox_view',
}; };
@@ -102,18 +102,3 @@ export const OPEN_AI_EVENTS = Object.freeze({
export const GENERAL_EVENTS = Object.freeze({ export const GENERAL_EVENTS = Object.freeze({
COMMAND_BAR: 'Used commandbar', COMMAND_BAR: 'Used commandbar',
}); });
export const INBOX_EVENTS = Object.freeze({
OPEN_CONVERSATION_VIA_INBOX: 'Opened conversation via inbox',
MARK_NOTIFICATION_AS_READ: 'Marked notification as read',
MARK_ALL_NOTIFICATIONS_AS_READ: 'Marked all notifications as read',
MARK_NOTIFICATION_AS_UNREAD: 'Marked notification as unread',
DELETE_NOTIFICATION: 'Deleted notification',
DELETE_ALL_NOTIFICATIONS: 'Deleted all notifications',
});
export const SLA_EVENTS = Object.freeze({
CREATE: 'Created an SLA',
UPDATE: 'Updated an SLA',
DELETED: 'Deleted an SLA',
});
@@ -24,7 +24,6 @@ class ActionCableConnector extends BaseActionCableConnector {
'conversation.mentioned': this.onConversationMentioned, 'conversation.mentioned': this.onConversationMentioned,
'notification.created': this.onNotificationCreated, 'notification.created': this.onNotificationCreated,
'notification.deleted': this.onNotificationDeleted, 'notification.deleted': this.onNotificationDeleted,
'notification.updated': this.onNotificationUpdated,
'first.reply.created': this.onFirstReplyCreated, 'first.reply.created': this.onFirstReplyCreated,
'conversation.read': this.onConversationRead, 'conversation.read': this.onConversationRead,
'conversation.updated': this.onConversationUpdated, 'conversation.updated': this.onConversationUpdated,
@@ -201,10 +200,6 @@ class ActionCableConnector extends BaseActionCableConnector {
this.app.$store.dispatch('notifications/deleteNotification', data); this.app.$store.dispatch('notifications/deleteNotification', data);
}; };
onNotificationUpdated = data => {
this.app.$store.dispatch('notifications/updateNotification', data);
};
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
onFirstReplyCreated = () => { onFirstReplyCreated = () => {
bus.$emit('fetch_overview_reports'); bus.$emit('fetch_overview_reports');
@@ -86,6 +86,3 @@ export const getConversationDashboardRoute = routeName => {
return null; return null;
} }
}; };
export const isAInboxViewRoute = routeName =>
['inbox_view_conversation'].includes(routeName);
@@ -55,7 +55,7 @@ export const findSnoozeTime = (snoozeType, currentDate = new Date()) => {
return parsedDate ? getUnixTime(parsedDate) : null; return parsedDate ? getUnixTime(parsedDate) : null;
}; };
export const snoozedReopenTime = snoozedUntil => { export const conversationReopenTime = snoozedUntil => {
if (!snoozedUntil) { if (!snoozedUntil) {
return null; return null;
} }
@@ -5,7 +5,6 @@ import {
isAConversationRoute, isAConversationRoute,
routeIsAccessibleFor, routeIsAccessibleFor,
validateLoggedInRoutes, validateLoggedInRoutes,
isAInboxViewRoute,
} from '../routeHelpers'; } from '../routeHelpers';
describe('#getCurrentAccount', () => { describe('#getCurrentAccount', () => {
@@ -135,10 +134,3 @@ describe('getConversationDashboardRoute', () => {
expect(getConversationDashboardRoute('non_existent_route')).toBeNull(); expect(getConversationDashboardRoute('non_existent_route')).toBeNull();
}); });
}); });
describe('isAInboxViewRoute', () => {
it('returns true if inbox view route name is provided', () => {
expect(isAInboxViewRoute('inbox_view_conversation')).toBe(true);
expect(isAInboxViewRoute('inbox_conversation')).toBe(false);
});
});
@@ -1,6 +1,6 @@
import { import {
findSnoozeTime, findSnoozeTime,
snoozedReopenTime, conversationReopenTime,
findStartOfNextWeek, findStartOfNextWeek,
findStartOfNextMonth, findStartOfNextMonth,
findNextDay, findNextDay,
@@ -88,13 +88,13 @@ describe('#Snooze Helpers', () => {
}); });
}); });
describe('snoozedReopenTime', () => { describe('conversationReopenTime', () => {
it('should return nil if snoozedUntil is nil', () => { it('should return nil if snoozedUntil is nil', () => {
expect(snoozedReopenTime(null)).toEqual(null); expect(conversationReopenTime(null)).toEqual(null);
}); });
it('should return formatted date if snoozedUntil is not nil', () => { it('should return formatted date if snoozedUntil is not nil', () => {
expect(snoozedReopenTime('2023-06-07T09:00:00.000Z')).toEqual( expect(conversationReopenTime('2023-06-07T09:00:00.000Z')).toEqual(
'7 Jun, 9.00am' '7 Jun, 9.00am'
); );
}); });
@@ -112,8 +112,7 @@
"REMOVE_LABEL": "Remove label from the conversation", "REMOVE_LABEL": "Remove label from the conversation",
"SETTINGS": "Settings", "SETTINGS": "Settings",
"AI_ASSIST": "AI Assist", "AI_ASSIST": "AI Assist",
"APPEARANCE": "Appearance", "APPEARANCE": "Appearance"
"SNOOZE_NOTIFICATION": "Snooze Notification"
}, },
"COMMANDS": { "COMMANDS": {
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard", "GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
@@ -154,8 +153,7 @@
"CHANGE_APPEARANCE": "Change Appearance", "CHANGE_APPEARANCE": "Change Appearance",
"LIGHT_MODE": "Light", "LIGHT_MODE": "Light",
"DARK_MODE": "Dark", "DARK_MODE": "Dark",
"SYSTEM_MODE": "System", "SYSTEM_MODE": "System"
"SNOOZE_NOTIFICATION": "Snooze Notification"
} }
}, },
"DASHBOARD_APPS": { "DASHBOARD_APPS": {
@@ -1,60 +0,0 @@
{
"INBOX": {
"LIST": {
"TITLE": "Inbox",
"DISPLAY_DROPDOWN": "Display",
"LOADING": "Fetching notifications",
"EOF": "All notifications loaded 🎉",
"404": "There are no active notifications in this group.",
"NO_NOTIFICATIONS": "No notifications",
"NOTE": "Notifications from all subscribed inboxes",
"SNOOZED_UNTIL": "Snoozed until",
"SNOOZED_UNTIL_TOMORROW": "Snoozed until tomorrow",
"SNOOZED_UNTIL_NEXT_WEEK": "Snoozed until next week"
},
"ACTION_HEADER": {
"SNOOZE": "Snooze notification",
"DELETE": "Delete notification"
},
"TYPES": {
"CONVERSATION_MENTION": "You have been mentioned in a conversation",
"CONVERSATION_CREATION": "New conversation created",
"CONVERSATION_ASSIGNMENT": "A conversation has been assigned to you",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "New message in an assigned conversation",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "New message in a conversation you are participating in"
},
"MENU_ITEM": {
"MARK_AS_READ": "Mark as read",
"MARK_AS_UNREAD": "Mark as unread",
"SNOOZE": "Snooze",
"DELETE": "Delete",
"MARK_ALL_READ": "Mark all as read",
"DELETE_ALL": "Delete all",
"DELETE_ALL_READ": "Delete all read"
},
"DISPLAY_MENU": {
"SORT": "Sort",
"DISPLAY": "Display :",
"SORT_OPTIONS": {
"NEWEST": "Newest",
"OLDEST": "Oldest",
"PRIORITY": "Priority"
},
"DISPLAY_OPTIONS": {
"SNOOZED": "Snoozed",
"READ": "Read",
"LABELS": "Labels",
"CONVERSATION_ID": "Conversation ID"
}
},
"ALERTS": {
"MARK_AS_READ": "Notification marked as read",
"MARK_AS_UNREAD": "Notification marked as unread",
"SNOOZE": "Notification snoozed",
"DELETE": "Notification deleted",
"MARK_ALL_READ": "All notifications marked as read",
"DELETE_ALL": "All notifications deleted",
"DELETE_ALL_READ": "All read notifications deleted"
}
}
}
@@ -29,8 +29,6 @@ import settings from './settings.json';
import signup from './signup.json'; import signup from './signup.json';
import teamsSettings from './teamsSettings.json'; import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json'; import whatsappTemplates from './whatsappTemplates.json';
import sla from './sla.json';
import inbox from './inbox.json';
export default { export default {
...advancedFilters, ...advancedFilters,
@@ -62,8 +60,6 @@ export default {
...setNewPassword, ...setNewPassword,
...settings, ...settings,
...signup, ...signup,
...sla,
...teamsSettings, ...teamsSettings,
...whatsappTemplates, ...whatsappTemplates,
...inbox,
}; };
@@ -156,9 +156,6 @@
"TRIAL_MESSAGE": "days trial remaining.", "TRIAL_MESSAGE": "days trial remaining.",
"TRAIL_BUTTON": "Buy Now", "TRAIL_BUTTON": "Buy Now",
"DELETED_USER": "Deleted User", "DELETED_USER": "Deleted User",
"EMAIL_VERIFICATION_PENDING": "It seems that you haven't verified your email address yet. Please check your inbox for the verification email.",
"RESEND_VERIFICATION_MAIL": "Resend verification email",
"EMAIL_VERIFICATION_SENT": "Verification email has been sent. Please check your inbox.",
"ACCOUNT_SUSPENDED": { "ACCOUNT_SUSPENDED": {
"TITLE": "Account Suspended", "TITLE": "Account Suspended",
"MESSAGE": "Your account is suspended. Please reach out to the support team for more information." "MESSAGE": "Your account is suspended. Please reach out to the support team for more information."
@@ -196,7 +193,6 @@
"CURRENTLY_VIEWING_ACCOUNT": "Currently viewing:", "CURRENTLY_VIEWING_ACCOUNT": "Currently viewing:",
"SWITCH": "Switch", "SWITCH": "Switch",
"CONVERSATIONS": "Conversations", "CONVERSATIONS": "Conversations",
"INBOX": "Inbox",
"ALL_CONVERSATIONS": "All Conversations", "ALL_CONVERSATIONS": "All Conversations",
"MENTIONED_CONVERSATIONS": "Mentions", "MENTIONED_CONVERSATIONS": "Mentions",
"PARTICIPATING_CONVERSATIONS": "Participating", "PARTICIPATING_CONVERSATIONS": "Participating",
@@ -238,7 +234,6 @@
"REPORTS_INBOX": "Inbox", "REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team", "REPORTS_TEAM": "Team",
"SET_AVAILABILITY_TITLE": "Set yourself as", "SET_AVAILABILITY_TITLE": "Set yourself as",
"SLA": "SLA",
"BETA": "Beta", "BETA": "Beta",
"REPORTS_OVERVIEW": "Overview", "REPORTS_OVERVIEW": "Overview",
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services", "FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
@@ -1,89 +0,0 @@
{
"SLA": {
"HEADER": "SLA",
"HEADER_BTN_TXT": "Add sla",
"LOADING": "Fetching slas",
"SEARCH_404": "There are no items matching this query",
"SIDEBAR_TXT": "<p><b>SLA</b> <p>Think of Service Level Agreements (SLAs) like friendly promises between a service provider and a customer.</p> <p> These promises set clear expectations for things like how quickly the team will respond to issues, making sure you always get a reliable and top-notch experience!</p>",
"LIST": {
"404": "There are no slas available in this account.",
"TITLE": "Manage slas",
"DESC": "SLAs: Friendly promises for great service!",
"TABLE_HEADER": ["Name", "Description", "FRT", "NRT", "RT", "Business Hours"]
},
"FORM": {
"NAME": {
"LABEL": "SLA Name",
"PLACEHOLDER": "SLA Name",
"REQUIRED_ERROR": "SLA name is required",
"MINIMUM_LENGTH_ERROR": "Minimum length 2 is required",
"VALID_ERROR": "Only Alphabets, Numbers, Hyphen and Underscore are allowed"
},
"DESCRIPTION": {
"LABEL": "Description",
"PLACEHOLDER": "SLA Description"
},
"FIRST_RESPONSE_TIME": {
"LABEL": "First Response Time",
"PLACEHOLDER": "First Response Time"
},
"NEXT_RESPONSE_TIME": {
"LABEL": "Next Response Time",
"PLACEHOLDER": "Next Response Time"
},
"RESOLUTION_TIME": {
"LABEL": "Resolution Time",
"PLACEHOLDER": "Resolution Time"
},
"BUSINESS_HOURS": {
"LABEL": "Business Hours",
"PLACEHOLDER": "Business Hours"
},
"EDIT": "Edit",
"CREATE": "Create",
"DELETE": "Delete",
"CANCEL": "Cancel"
},
"SUGGESTIONS": {
"TOOLTIP": {
"SINGLE_SUGGESTION": "Add sla to conversation",
"MULTIPLE_SUGGESTION": "Select this sla",
"DESELECT": "Deselect sla",
"DISMISS": "Dismiss suggestion"
},
"POWERED_BY": "Chatwoot AI",
"DISMISS": "Dismiss",
"ADD_SELECTED_SLAS": "Add selected slas",
"ADD_SELECTED_SLA": "Add selected sla",
"ADD_ALL_SLAS": "Add all slas"
},
"ADD": {
"TITLE": "Add SLA",
"DESC": "SLAs: Friendly promises for great service!",
"API": {
"SUCCESS_MESSAGE": "SLA added successfully",
"ERROR_MESSAGE": "There was an error, please try again"
}
},
"EDIT": {
"TITLE": "Edit SLA",
"API": {
"SUCCESS_MESSAGE": "SLA updated successfully",
"ERROR_MESSAGE": "There was an error, please try again"
}
},
"DELETE": {
"BUTTON_TEXT": "Delete",
"API": {
"SUCCESS_MESSAGE": "SLA deleted successfully",
"ERROR_MESSAGE": "There was an error, please try again"
},
"CONFIRM": {
"TITLE": "Confirm Deletion",
"MESSAGE": "Are you sure to delete ",
"YES": "Yes, Delete ",
"NO": "No, Keep "
}
}
}
}
@@ -150,8 +150,8 @@ export default {
} }
}, },
submitAutomation() { submitAutomation() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) return; if (this.v$.$invalid) return;
const automation = generateAutomationPayload(this.automation); const automation = generateAutomationPayload(this.automation);
this.$emit('saveAutomation', automation, this.mode); 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 { export default {
setup() {
return { v$: useVuelidate() };
},
validations: { validations: {
automation: { automation: {
name: { name: {
@@ -52,77 +52,19 @@ describe('#dateFormat', () => {
}); });
describe('#shortTimestamp', () => { describe('#shortTimestamp', () => {
// Test cases when withAgo is false or not provided it('returns correct value', () => {
it('returns correct value without ago', () => {
expect(TimeMixin.methods.shortTimestamp('less than a minute ago')).toEqual( expect(TimeMixin.methods.shortTimestamp('less than a minute ago')).toEqual(
'now' 'now'
); );
expect(TimeMixin.methods.shortTimestamp('1 minute ago')).toEqual('1m'); expect(TimeMixin.methods.shortTimestamp(' minute ago')).toEqual('m');
expect(TimeMixin.methods.shortTimestamp('12 minutes ago')).toEqual('12m'); expect(TimeMixin.methods.shortTimestamp(' minutes ago')).toEqual('m');
expect(TimeMixin.methods.shortTimestamp('a minute ago')).toEqual('1m'); expect(TimeMixin.methods.shortTimestamp(' hour ago')).toEqual('h');
expect(TimeMixin.methods.shortTimestamp('an hour ago')).toEqual('1h'); expect(TimeMixin.methods.shortTimestamp(' hours ago')).toEqual('h');
expect(TimeMixin.methods.shortTimestamp('1 hour ago')).toEqual('1h'); expect(TimeMixin.methods.shortTimestamp(' day ago')).toEqual('d');
expect(TimeMixin.methods.shortTimestamp('2 hours ago')).toEqual('2h'); expect(TimeMixin.methods.shortTimestamp(' days ago')).toEqual('d');
expect(TimeMixin.methods.shortTimestamp('1 day ago')).toEqual('1d'); expect(TimeMixin.methods.shortTimestamp(' month ago')).toEqual('mo');
expect(TimeMixin.methods.shortTimestamp('a day ago')).toEqual('1d'); expect(TimeMixin.methods.shortTimestamp(' months ago')).toEqual('mo');
expect(TimeMixin.methods.shortTimestamp('3 days ago')).toEqual('3d'); expect(TimeMixin.methods.shortTimestamp(' year ago')).toEqual('y');
expect(TimeMixin.methods.shortTimestamp('a month ago')).toEqual('1mo'); expect(TimeMixin.methods.shortTimestamp(' years ago')).toEqual('y');
expect(TimeMixin.methods.shortTimestamp('1 month ago')).toEqual('1mo');
expect(TimeMixin.methods.shortTimestamp('2 months ago')).toEqual('2mo');
expect(TimeMixin.methods.shortTimestamp('a year ago')).toEqual('1y');
expect(TimeMixin.methods.shortTimestamp('1 year ago')).toEqual('1y');
expect(TimeMixin.methods.shortTimestamp('4 years ago')).toEqual('4y');
});
// Test cases when withAgo is true
it('returns correct value with ago', () => {
expect(
TimeMixin.methods.shortTimestamp('less than a minute ago', true)
).toEqual('now');
expect(TimeMixin.methods.shortTimestamp('1 minute ago', true)).toEqual(
'1m ago'
);
expect(TimeMixin.methods.shortTimestamp('12 minutes ago', true)).toEqual(
'12m ago'
);
expect(TimeMixin.methods.shortTimestamp('a minute ago', true)).toEqual(
'1m ago'
);
expect(TimeMixin.methods.shortTimestamp('an hour ago', true)).toEqual(
'1h ago'
);
expect(TimeMixin.methods.shortTimestamp('1 hour ago', true)).toEqual(
'1h ago'
);
expect(TimeMixin.methods.shortTimestamp('2 hours ago', true)).toEqual(
'2h ago'
);
expect(TimeMixin.methods.shortTimestamp('1 day ago', true)).toEqual(
'1d ago'
);
expect(TimeMixin.methods.shortTimestamp('a day ago', true)).toEqual(
'1d ago'
);
expect(TimeMixin.methods.shortTimestamp('3 days ago', true)).toEqual(
'3d ago'
);
expect(TimeMixin.methods.shortTimestamp('a month ago', true)).toEqual(
'1mo ago'
);
expect(TimeMixin.methods.shortTimestamp('1 month ago', true)).toEqual(
'1mo ago'
);
expect(TimeMixin.methods.shortTimestamp('2 months ago', true)).toEqual(
'2mo ago'
);
expect(TimeMixin.methods.shortTimestamp('a year ago', true)).toEqual(
'1y ago'
);
expect(TimeMixin.methods.shortTimestamp('1 year ago', true)).toEqual(
'1y ago'
);
expect(TimeMixin.methods.shortTimestamp('4 years ago', true)).toEqual(
'4y ago'
);
}); });
}); });
+17 -28
View File
@@ -28,36 +28,25 @@ export default {
const unixTime = fromUnixTime(time); const unixTime = fromUnixTime(time);
return format(unixTime, dateFormat); return format(unixTime, dateFormat);
}, },
shortTimestamp(time, withAgo = false) { shortTimestamp(time) {
// This function takes a time string and converts it to a short time string
// with the following format: 1m, 1h, 1d, 1mo, 1y
// The function also takes an optional boolean parameter withAgo
// which will add the word "ago" to the end of the time string
const suffix = withAgo ? ' ago' : '';
const timeMappings = {
'less than a minute ago': 'now',
'a minute ago': `1m${suffix}`,
'an hour ago': `1h${suffix}`,
'a day ago': `1d${suffix}`,
'a month ago': `1mo${suffix}`,
'a year ago': `1y${suffix}`,
};
// Check if the time string is one of the specific cases
if (timeMappings[time]) {
return timeMappings[time];
}
const convertToShortTime = time const convertToShortTime = time
.replace(/about|over|almost|/g, '') .replace(/about|over|almost|/g, '')
.replace(' minute ago', `m${suffix}`) .replace('less than a minute ago', 'now')
.replace(' minutes ago', `m${suffix}`) .replace(' minute ago', 'm')
.replace(' hour ago', `h${suffix}`) .replace(' minutes ago', 'm')
.replace(' hours ago', `h${suffix}`) .replace('a minute ago', 'm')
.replace(' day ago', `d${suffix}`) .replace('an hour ago', 'h')
.replace(' days ago', `d${suffix}`) .replace(' hour ago', 'h')
.replace(' month ago', `mo${suffix}`) .replace(' hours ago', 'h')
.replace(' months ago', `mo${suffix}`) .replace(' day ago', 'd')
.replace(' year ago', `y${suffix}`) .replace('a day ago', 'd')
.replace(' years ago', `y${suffix}`); .replace(' days ago', 'd')
.replace('a month ago', 'mo')
.replace(' months ago', 'mo')
.replace(' month ago', 'mo')
.replace('a year ago', 'y')
.replace(' year ago', 'y')
.replace(' years ago', 'y');
return convertToShortTime; return convertToShortTime;
}, },
}, },
@@ -8,12 +8,12 @@
<form class="w-full" @submit.prevent="addCustomAttribute"> <form class="w-full" @submit.prevent="addCustomAttribute">
<woot-input <woot-input
v-model.trim="attributeName" v-model.trim="attributeName"
:class="{ error: $v.attributeName.$error }" :class="{ error: v$.attributeName.$error }"
class="w-full" class="w-full"
:error="attributeNameError" :error="attributeNameError"
:label="$t('CUSTOM_ATTRIBUTES.FORM.NAME.LABEL')" :label="$t('CUSTOM_ATTRIBUTES.FORM.NAME.LABEL')"
:placeholder="$t('CUSTOM_ATTRIBUTES.FORM.NAME.PLACEHOLDER')" :placeholder="$t('CUSTOM_ATTRIBUTES.FORM.NAME.PLACEHOLDER')"
@input="$v.attributeName.$touch" @input="v$.attributeName.$touch"
/> />
<woot-input <woot-input
v-model.trim="attributeValue" v-model.trim="attributeValue"
@@ -23,7 +23,7 @@
/> />
<div class="flex justify-end items-center py-2 px-0 gap-2"> <div class="flex justify-end items-center py-2 px-0 gap-2">
<woot-button <woot-button
:is-disabled="$v.attributeName.$invalid || isCreating" :is-disabled="v$.attributeName.$invalid || isCreating"
:is-loading="isCreating" :is-loading="isCreating"
> >
{{ $t('CUSTOM_ATTRIBUTES.FORM.CREATE') }} {{ $t('CUSTOM_ATTRIBUTES.FORM.CREATE') }}
@@ -39,7 +39,8 @@
<script> <script>
import Modal from 'dashboard/components/Modal.vue'; import Modal from 'dashboard/components/Modal.vue';
import alertMixin from 'shared/mixins/alertMixin'; 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 { export default {
components: { components: {
@@ -56,6 +57,9 @@ export default {
default: false, default: false,
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
attributeValue: '', attributeValue: '',
@@ -70,7 +74,7 @@ export default {
}, },
computed: { computed: {
attributeNameError() { attributeNameError() {
if (this.$v.attributeName.$error) { if (this.v$.attributeName.$error) {
return this.$t('CUSTOM_ATTRIBUTES.FORM.NAME.ERROR'); return this.$t('CUSTOM_ATTRIBUTES.FORM.NAME.ERROR');
} }
return ''; return '';
@@ -4,7 +4,7 @@
<div> <div>
<div <div
class="mt-1 multiselect-wrap--medium" class="mt-1 multiselect-wrap--medium"
:class="{ error: $v.parentContact.$error }" :class="{ error: v$.parentContact.$error }"
> >
<label class="multiselect__label"> <label class="multiselect__label">
{{ $t('MERGE_CONTACTS.PARENT.TITLE') }} {{ $t('MERGE_CONTACTS.PARENT.TITLE') }}
@@ -52,7 +52,7 @@
{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }} {{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}
</span> </span>
</multiselect> </multiselect>
<span v-if="$v.parentContact.$error" class="message"> <span v-if="v$.parentContact.$error" class="message">
{{ $t('MERGE_CONTACTS.FORM.CHILD_CONTACT.ERROR') }} {{ $t('MERGE_CONTACTS.FORM.CHILD_CONTACT.ERROR') }}
</span> </span>
</div> </div>
@@ -115,7 +115,8 @@
<script> <script>
import alertMixin from 'shared/mixins/alertMixin'; 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 MergeContactSummary from 'dashboard/modules/contact/components/MergeContactSummary.vue';
import ContactDropdownItem from './ContactDropdownItem.vue'; import ContactDropdownItem from './ContactDropdownItem.vue';
@@ -141,6 +142,10 @@ export default {
default: () => [], default: () => [],
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
validations: { validations: {
primaryContact: { primaryContact: {
required, required,
@@ -165,8 +170,8 @@ export default {
this.$emit('search', query); this.$emit('search', query);
}, },
onSubmit() { onSubmit() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) { if (this.v$.$invalid) {
return; return;
} }
this.$emit('submit', this.parentContact.id); this.$emit('submit', this.parentContact.id);
@@ -45,7 +45,8 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~dashboard/assets/scss/variables.scss'; @import 'dashboard/assets/scss/variables.scss';
.footer-wrap { .footer-wrap {
width: 100%; width: 100%;
display: flex; display: flex;
@@ -71,5 +71,3 @@ export const ICON_APPEARANCE = `<svg role="img" class="ninja-icon ninja-icon--fl
export const ICON_LIGHT_MODE = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 2Zm5 10a5 5 0 1 1-10 0a5 5 0 0 1 10 0Zm4.25.75a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5h1.5ZM12 19a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 19Zm-7.75-6.25a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5h1.5Zm-.03-8.53a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-1.5-1.5a.75.75 0 0 1 0-1.06Zm1.06 15.56a.75.75 0 1 1-1.06-1.06l1.5-1.5a.75.75 0 1 1 1.06 1.06l-1.5 1.5Zm14.5-15.56a.75.75 0 0 0-1.06 0l-1.5 1.5a.75.75 0 0 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06Zm-1.06 15.56a.75.75 0 1 0 1.06-1.06l-1.5-1.5a.75.75 0 1 0-1.06 1.06l1.5 1.5Z"/></svg>`; export const ICON_LIGHT_MODE = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 2Zm5 10a5 5 0 1 1-10 0a5 5 0 0 1 10 0Zm4.25.75a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5h1.5ZM12 19a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 12 19Zm-7.75-6.25a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5h1.5Zm-.03-8.53a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-1.5-1.5a.75.75 0 0 1 0-1.06Zm1.06 15.56a.75.75 0 1 1-1.06-1.06l1.5-1.5a.75.75 0 1 1 1.06 1.06l-1.5 1.5Zm14.5-15.56a.75.75 0 0 0-1.06 0l-1.5 1.5a.75.75 0 0 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06Zm-1.06 15.56a.75.75 0 1 0 1.06-1.06l-1.5-1.5a.75.75 0 1 0-1.06 1.06l1.5 1.5Z"/></svg>`;
export const ICON_DARK_MODE = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M20.026 17.001c-2.762 4.784-8.879 6.423-13.663 3.661A9.965 9.965 0 0 1 3.13 17.68a.75.75 0 0 1 .365-1.132c3.767-1.348 5.785-2.91 6.956-5.146c1.232-2.353 1.551-4.93.689-8.463a.75.75 0 0 1 .769-.927a9.961 9.961 0 0 1 4.457 1.327c4.784 2.762 6.423 8.879 3.66 13.662Zm-8.248-4.903c-1.25 2.389-3.31 4.1-6.817 5.499a8.49 8.49 0 0 0 2.152 1.766a8.502 8.502 0 0 0 8.502-14.725a8.484 8.484 0 0 0-2.792-1.015c.647 3.384.23 6.043-1.045 8.475Z"/></svg>`; export const ICON_DARK_MODE = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M20.026 17.001c-2.762 4.784-8.879 6.423-13.663 3.661A9.965 9.965 0 0 1 3.13 17.68a.75.75 0 0 1 .365-1.132c3.767-1.348 5.785-2.91 6.956-5.146c1.232-2.353 1.551-4.93.689-8.463a.75.75 0 0 1 .769-.927a9.961 9.961 0 0 1 4.457 1.327c4.784 2.762 6.423 8.879 3.66 13.662Zm-8.248-4.903c-1.25 2.389-3.31 4.1-6.817 5.499a8.49 8.49 0 0 0 2.152 1.766a8.502 8.502 0 0 0 8.502-14.725a8.484 8.484 0 0 0-2.792-1.015c.647 3.384.23 6.043-1.045 8.475Z"/></svg>`;
export const ICON_SYSTEM_MODE = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M4.25 3A2.25 2.25 0 0 0 2 5.25v10.5A2.25 2.25 0 0 0 4.25 18H9.5v1.25c0 .69-.56 1.25-1.25 1.25h-.5a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-.5c-.69 0-1.25-.56-1.25-1.25V18h5.25A2.25 2.25 0 0 0 22 15.75V5.25A2.25 2.25 0 0 0 19.75 3H4.25ZM13 18v1.25c0 .45.108.875.3 1.25h-2.6c.192-.375.3-.8.3-1.25V18h2ZM3.5 5.25a.75.75 0 0 1 .75-.75h15.5a.75.75 0 0 1 .75.75V13h-17V5.25Zm0 9.25h17v1.25a.75.75 0 0 1-.75.75H4.25a.75.75 0 0 1-.75-.75V14.5Z"/></svg>`; export const ICON_SYSTEM_MODE = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M4.25 3A2.25 2.25 0 0 0 2 5.25v10.5A2.25 2.25 0 0 0 4.25 18H9.5v1.25c0 .69-.56 1.25-1.25 1.25h-.5a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-.5c-.69 0-1.25-.56-1.25-1.25V18h5.25A2.25 2.25 0 0 0 22 15.75V5.25A2.25 2.25 0 0 0 19.75 3H4.25ZM13 18v1.25c0 .45.108.875.3 1.25h-2.6c.192-.375.3-.8.3-1.25V18h2ZM3.5 5.25a.75.75 0 0 1 .75-.75h15.5a.75.75 0 0 1 .75.75V13h-17V5.25Zm0 9.25h17v1.25a.75.75 0 0 1-.75.75H4.25a.75.75 0 0 1-.75-.75V14.5Z"/></svg>`;
export const ICON_SNOOZE_NOTIFICATION = `<svg role="img" class="ninja-icon ninja-icon--fluent" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path fill="currentColor" d="M12 3.5c-3.104 0-6 2.432-6 6.25v4.153L4.682 17h14.67l-1.354-3.093V11.75a.75.75 0 0 1 1.5 0v1.843l1.381 3.156a1.25 1.25 0 0 1-1.145 1.751H15a3.002 3.002 0 0 1-6.003 0H4.305a1.25 1.25 0 0 1-1.15-1.739l1.344-3.164V9.75C4.5 5.068 8.103 2 12 2c.86 0 1.705.15 2.5.432a.75.75 0 0 1-.502 1.413A5.964 5.964 0 0 0 12 3.5ZM12 20c.828 0 1.5-.671 1.501-1.5h-3.003c0 .829.673 1.5 1.502 1.5Zm3.25-13h-2.5l-.101.007A.75.75 0 0 0 12.75 8.5h1.043l-1.653 2.314l-.055.09A.75.75 0 0 0 12.75 12h2.5l.102-.007a.75.75 0 0 0-.102-1.493h-1.042l1.653-2.314l.055-.09A.75.75 0 0 0 15.25 7Zm6-5h-3.5l-.101.007A.75.75 0 0 0 17.75 3.5h2.134l-2.766 4.347l-.05.09A.75.75 0 0 0 17.75 9h3.5l.102-.007A.75.75 0 0 0 21.25 7.5h-2.133l2.766-4.347l.05-.09A.75.75 0 0 0 21.25 2Z"/></svg>`;
@@ -15,6 +15,3 @@ export const CMD_REOPEN_CONVERSATION = 'CMD_REOPEN_CONVERSATION';
export const CMD_RESOLVE_CONVERSATION = 'CMD_RESOLVE_CONVERSATION'; export const CMD_RESOLVE_CONVERSATION = 'CMD_RESOLVE_CONVERSATION';
export const CMD_SNOOZE_CONVERSATION = 'CMD_SNOOZE_CONVERSATION'; export const CMD_SNOOZE_CONVERSATION = 'CMD_SNOOZE_CONVERSATION';
export const CMD_AI_ASSIST = 'CMD_AI_ASSIST'; export const CMD_AI_ASSIST = 'CMD_AI_ASSIST';
// Inbox Commands (Notifications)
export const CMD_SNOOZE_NOTIFICATION = 'CMD_SNOOZE_NOTIFICATION';
@@ -12,7 +12,6 @@
<script> <script>
import 'ninja-keys'; import 'ninja-keys';
import conversationHotKeysMixin from './conversationHotKeys'; import conversationHotKeysMixin from './conversationHotKeys';
import inboxHotKeysMixin from './inboxHotKeys';
import goToCommandHotKeys from './goToCommandHotKeys'; import goToCommandHotKeys from './goToCommandHotKeys';
import appearanceHotKeys from './appearanceHotKeys'; import appearanceHotKeys from './appearanceHotKeys';
import agentMixin from 'dashboard/mixins/agentMixin'; import agentMixin from 'dashboard/mixins/agentMixin';
@@ -26,7 +25,6 @@ export default {
adminMixin, adminMixin,
agentMixin, agentMixin,
conversationHotKeysMixin, conversationHotKeysMixin,
inboxHotKeysMixin,
conversationLabelMixin, conversationLabelMixin,
conversationTeamMixin, conversationTeamMixin,
appearanceHotKeys, appearanceHotKeys,
@@ -44,7 +42,6 @@ export default {
}, },
hotKeys() { hotKeys() {
return [ return [
...this.inboxHotKeys,
...this.conversationHotKeys, ...this.conversationHotKeys,
...this.goToCommandHotKeys, ...this.goToCommandHotKeys,
...this.goToAppearanceHotKeys, ...this.goToAppearanceHotKeys,
@@ -30,10 +30,7 @@ import {
UNMUTE_ACTION, UNMUTE_ACTION,
MUTE_ACTION, MUTE_ACTION,
} from './commandBarActions'; } from './commandBarActions';
import { import { isAConversationRoute } from '../../../helper/routeHelpers';
isAConversationRoute,
isAInboxViewRoute,
} from '../../../helper/routeHelpers';
export default { export default {
mixins: [aiMixin], mixins: [aiMixin],
watch: { watch: {
@@ -328,10 +325,7 @@ export default {
}, },
conversationHotKeys() { conversationHotKeys() {
if ( if (isAConversationRoute(this.$route.name)) {
isAConversationRoute(this.$route.name) ||
isAInboxViewRoute(this.$route.name)
) {
const defaultConversationHotKeys = [ const defaultConversationHotKeys = [
...this.statusActions, ...this.statusActions,
...this.conversationAdditionalActions, ...this.conversationAdditionalActions,
@@ -1,81 +0,0 @@
import wootConstants from 'dashboard/constants/globals';
import { CMD_SNOOZE_NOTIFICATION } from './commandBarBusEvents';
import { ICON_SNOOZE_NOTIFICATION } from './CommandBarIcons';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
const INBOX_SNOOZE_EVENTS = [
{
id: 'snooze_notification',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_NOTIFICATION',
icon: ICON_SNOOZE_NOTIFICATION,
children: Object.values(SNOOZE_OPTIONS),
},
{
id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
parent: 'snooze_notification',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
},
{
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
{
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
title: 'COMMAND_BAR.COMMANDS.CUSTOM',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
bus.$emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
},
];
export default {
computed: {
inboxHotKeys() {
if (isAInboxViewRoute(this.$route.name)) {
return this.prepareActions(INBOX_SNOOZE_EVENTS);
}
return [];
},
},
methods: {
prepareActions(actions) {
return actions.map(action => ({
...action,
title: this.$t(action.title),
section: this.$t(action.section),
}));
},
},
};
@@ -41,7 +41,7 @@
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)" :dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
:show-query-operator="i !== appliedFilters.length - 1" :show-query-operator="i !== appliedFilters.length - 1"
:show-user-input="showUserInput(appliedFilters[i].filter_operator)" :show-user-input="showUserInput(appliedFilters[i].filter_operator)"
:v="$v.appliedFilters.$each[i]" :v="v$.appliedFilters.$each[i]"
@resetFilter="resetFilter(i, appliedFilters[i])" @resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)" @removeFilter="removeFilter(i)"
/> />
@@ -90,7 +90,7 @@
<script> <script>
import alertMixin from 'shared/mixins/alertMixin'; 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 FilterInputBox from '../../../../components/widgets/FilterInput/Index.vue';
import countries from 'shared/constants/countries.js'; import countries from 'shared/constants/countries.js';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
@@ -98,6 +98,8 @@ import { filterAttributeGroups } from '../contactFilterItems';
import filterMixin from 'shared/mixins/filterMixin'; import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'; import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events'; import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
components: { components: {
FilterInputBox, FilterInputBox,
@@ -125,6 +127,9 @@ export default {
default: '', default: '',
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
validations: { validations: {
appliedFilters: { appliedFilters: {
required, required,
@@ -314,8 +319,8 @@ export default {
} }
}, },
submitFilterQuery() { submitFilterQuery() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) return; if (this.v$.$invalid) return;
this.$store.dispatch( this.$store.dispatch(
'contacts/setContactFilters', 'contacts/setContactFilters',
JSON.parse(JSON.stringify(this.appliedFilters)) JSON.parse(JSON.stringify(this.appliedFilters))
@@ -27,7 +27,7 @@
</section> </section>
</template> </template>
<script> <script lang="jsx">
import { mixin as clickaway } from 'vue-clickaway'; import { mixin as clickaway } from 'vue-clickaway';
import { VeTable } from 'vue-easytable'; import { VeTable } from 'vue-easytable';
import { getCountryFlag } from 'dashboard/helper/flag'; import { getCountryFlag } from 'dashboard/helper/flag';
@@ -18,38 +18,38 @@
</div> </div>
<div> <div>
<div class="w-full"> <div class="w-full">
<label :class="{ error: $v.name.$error }"> <label :class="{ error: v$.name.$error }">
{{ $t('CONTACT_FORM.FORM.NAME.LABEL') }} {{ $t('CONTACT_FORM.FORM.NAME.LABEL') }}
<input <input
v-model.trim="name" v-model.trim="name"
type="text" type="text"
:placeholder="$t('CONTACT_FORM.FORM.NAME.PLACEHOLDER')" :placeholder="$t('CONTACT_FORM.FORM.NAME.PLACEHOLDER')"
@input="$v.name.$touch" @input="v$.name.$touch"
/> />
</label> </label>
<label :class="{ error: $v.email.$error }"> <label :class="{ error: v$.email.$error }">
{{ $t('CONTACT_FORM.FORM.EMAIL_ADDRESS.LABEL') }} {{ $t('CONTACT_FORM.FORM.EMAIL_ADDRESS.LABEL') }}
<input <input
v-model.trim="email" v-model.trim="email"
type="text" type="text"
:placeholder="$t('CONTACT_FORM.FORM.EMAIL_ADDRESS.PLACEHOLDER')" :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') }} {{ $t('CONTACT_FORM.FORM.EMAIL_ADDRESS.ERROR') }}
</span> </span>
</label> </label>
</div> </div>
</div> </div>
<div class="w-full"> <div class="w-full">
<label :class="{ error: $v.description.$error }"> <label :class="{ error: v$.description.$error }">
{{ $t('CONTACT_FORM.FORM.BIO.LABEL') }} {{ $t('CONTACT_FORM.FORM.BIO.LABEL') }}
<textarea <textarea
v-model.trim="description" v-model.trim="description"
type="text" type="text"
:placeholder="$t('CONTACT_FORM.FORM.BIO.PLACEHOLDER')" :placeholder="$t('CONTACT_FORM.FORM.BIO.PLACEHOLDER')"
@input="$v.description.$touch" @input="v$.description.$touch"
/> />
</label> </label>
</div> </div>
@@ -67,7 +67,7 @@
:error="isPhoneNumberNotValid" :error="isPhoneNumberNotValid"
:placeholder="$t('CONTACT_FORM.FORM.PHONE_NUMBER.PLACEHOLDER')" :placeholder="$t('CONTACT_FORM.FORM.PHONE_NUMBER.PLACEHOLDER')"
@input="onPhoneNumberInputChange" @input="onPhoneNumberInputChange"
@blur="$v.phoneNumber.$touch" @blur="v$.phoneNumber.$touch"
@setCode="setPhoneCode" @setCode="setPhoneCode"
/> />
<span v-if="isPhoneNumberNotValid" class="message"> <span v-if="isPhoneNumberNotValid" class="message">
@@ -151,10 +151,11 @@ import {
DuplicateContactException, DuplicateContactException,
ExceptionWithMessage, ExceptionWithMessage,
} from 'shared/helpers/CustomErrors'; } 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 countries from 'shared/constants/countries.js';
import { isPhoneNumberValid } from 'shared/helpers/Validators'; import { isPhoneNumberValid } from 'shared/helpers/Validators';
import parsePhoneNumber from 'libphonenumber-js'; import parsePhoneNumber from 'libphonenumber-js';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
mixins: [alertMixin], mixins: [alertMixin],
@@ -172,6 +173,9 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
countries: countries, countries: countries,
@@ -364,8 +368,8 @@ export default {
} }
}, },
async handleSubmit() { async handleSubmit() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid || this.isPhoneNumberNotValid) { if (this.v$.$invalid || this.isPhoneNumberNotValid) {
return; return;
} }
try { try {
@@ -182,7 +182,6 @@ import { getCountryFlag } from 'dashboard/helper/flag';
import { BUS_EVENTS } from 'shared/constants/busEvents'; import { BUS_EVENTS } from 'shared/constants/busEvents';
import { import {
isAConversationRoute, isAConversationRoute,
isAInboxViewRoute,
getConversationDashboardRoute, getConversationDashboardRoute,
} from '../../../../helper/routeHelpers'; } from '../../../../helper/routeHelpers';
@@ -304,10 +303,6 @@ export default {
this.$router.push({ this.$router.push({
name: getConversationDashboardRoute(this.$route.name), name: getConversationDashboardRoute(this.$route.name),
}); });
} else if (isAInboxViewRoute(this.$route.name)) {
this.$router.push({
name: 'inbox_view',
});
} else if (this.$route.name !== 'contacts_dashboard') { } else if (this.$route.name !== 'contacts_dashboard') {
this.$router.push({ this.$router.push({
name: 'contacts_dashboard', name: 'contacts_dashboard',
@@ -13,7 +13,7 @@
</label> </label>
<div <div
class="multiselect-wrap--small" class="multiselect-wrap--small"
:class="{ 'has-multi-select-error': $v.targetInbox.$error }" :class="{ 'has-multi-select-error': v$.targetInbox.$error }"
> >
<multiselect <multiselect
v-model="targetInbox" v-model="targetInbox"
@@ -47,8 +47,8 @@
</template> </template>
</multiselect> </multiselect>
</div> </div>
<label :class="{ error: $v.targetInbox.$error }"> <label :class="{ error: v$.targetInbox.$error }">
<span v-if="$v.targetInbox.$error" class="message"> <span v-if="v$.targetInbox.$error" class="message">
{{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }} {{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }}
</span> </span>
</label> </label>
@@ -76,15 +76,15 @@
</div> </div>
<div v-if="isAnEmailInbox" class="w-full"> <div v-if="isAnEmailInbox" class="w-full">
<div 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') }} {{ $t('NEW_CONVERSATION.FORM.SUBJECT.LABEL') }}
<input <input
v-model="subject" v-model="subject"
type="text" type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.SUBJECT.PLACEHOLDER')" :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') }} {{ $t('NEW_CONVERSATION.FORM.SUBJECT.ERROR') }}
</span> </span>
</label> </label>
@@ -112,13 +112,13 @@
<woot-message-editor <woot-message-editor
v-model="message" v-model="message"
class="message-editor" class="message-editor"
:class="{ editor_warning: $v.message.$error }" :class="{ editor_warning: v$.message.$error }"
:enable-variables="true" :enable-variables="true"
:signature="signatureToApply" :signature="signatureToApply"
:allow-signature="true" :allow-signature="true"
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')" :placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
@toggle-canned-menu="toggleCannedMenu" @toggle-canned-menu="toggleCannedMenu"
@blur="$v.message.$touch" @blur="v$.message.$touch"
> >
<template #footer> <template #footer>
<message-signature-missing-alert <message-signature-missing-alert
@@ -138,7 +138,7 @@
</div> </div>
</template> </template>
</woot-message-editor> </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') }} {{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
</span> </span>
</div> </div>
@@ -149,16 +149,16 @@
@on-select-template="toggleWaTemplate" @on-select-template="toggleWaTemplate"
@on-send="onSendWhatsAppReply" @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') }} {{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
<textarea <textarea
v-model="message" v-model="message"
class="min-h-[5rem]" class="min-h-[5rem]"
type="text" type="text"
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')" :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') }} {{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
</span> </span>
</label> </label>
@@ -240,17 +240,17 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue'; import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead.vue'; import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead.vue';
import CannedResponse from 'dashboard/components/widgets/conversation/CannedResponse.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 InboxDropdownItem from 'dashboard/components/widgets/InboxDropdownItem.vue';
import WhatsappTemplates from './WhatsappTemplates.vue'; import WhatsappTemplates from './WhatsappTemplates.vue';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import { INBOX_TYPES } from 'shared/mixins/inboxMixin'; import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors'; import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
import { getInboxSource } from 'dashboard/helper/inbox'; 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 inboxMixin from 'shared/mixins/inboxMixin';
import FileUpload from 'vue-upload-component'; 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 { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin'; import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
import { import {
@@ -258,6 +258,7 @@ import {
removeSignature, removeSignature,
} from 'dashboard/helper/editorHelper'; } from 'dashboard/helper/editorHelper';
import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
components: { components: {
@@ -286,6 +287,9 @@ export default {
default: '', default: '',
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
name: '', name: '',
@@ -494,8 +498,8 @@ export default {
}, },
onFormSubmit() { onFormSubmit() {
const isFromWhatsApp = false; const isFromWhatsApp = false;
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) { if (this.v$.$invalid) {
return; return;
} }
this.createConversation({ this.createConversation({
@@ -571,6 +575,7 @@ export default {
.file-uploads { .file-uploads {
@apply text-start; @apply text-start;
} }
.multiselect-wrap--small.has-multi-select-error { .multiselect-wrap--small.has-multi-select-error {
::v-deep { ::v-deep {
.multiselect__tags { .multiselect__tags {
@@ -583,9 +588,11 @@ export default {
.mention--box { .mention--box {
@apply left-0 m-auto right-0 top-auto h-fit; @apply left-0 m-auto right-0 top-auto h-fit;
} }
.multiselect .multiselect__content .multiselect__option span { .multiselect .multiselect__content .multiselect__option span {
@apply inline-flex w-6 text-slate-600 dark:text-slate-400; @apply inline-flex w-6 text-slate-600 dark:text-slate-400;
} }
.multiselect .multiselect__content .multiselect__option { .multiselect .multiselect__content .multiselect__option {
@apply py-0.5 px-1; @apply py-0.5 px-1;
} }
@@ -1,28 +1,9 @@
/* eslint arrow-body-style: 0 */ /* eslint arrow-body-style: 0 */
import { frontendURL } from '../../../helper/URLHelper'; import { frontendURL } from '../../../helper/URLHelper';
const ConversationView = () => import('./ConversationView'); const ConversationView = () => import('./ConversationView.vue');
const InboxView = () => import('../inbox/InboxView.vue');
export default { export default {
routes: [ routes: [
{
path: frontendURL('accounts/:accountId/inbox-view'),
name: 'inbox_view',
roles: ['administrator', 'agent'],
component: InboxView,
props: () => {
return { inboxId: 0 };
},
},
{
path: frontendURL('accounts/:accountId/inbox-view/:conversation_id'),
name: 'inbox_view_conversation',
roles: ['administrator', 'agent'],
component: InboxView,
props: route => {
return { inboxId: 0, conversationId: route.params.conversation_id };
},
},
{ {
path: frontendURL('accounts/:accountId/dashboard'), path: frontendURL('accounts/:accountId/dashboard'),
name: 'home', name: 'home',
@@ -8,11 +8,11 @@
:label="$t('FILTER.CUSTOM_VIEWS.ADD.LABEL')" :label="$t('FILTER.CUSTOM_VIEWS.ADD.LABEL')"
type="text" type="text"
:error=" :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')" :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"> <div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
@@ -29,9 +29,10 @@
</template> </template>
<script> <script>
import { required, minLength } from 'vuelidate/lib/validators'; import { required, minLength } from '@vuelidate/validators';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import { CONTACTS_EVENTS } from '../../../helper/AnalyticsHelper/events'; import { CONTACTS_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
mixins: [alertMixin], mixins: [alertMixin],
@@ -49,7 +50,9 @@ export default {
default: () => {}, default: () => {},
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
show: true, show: true,
@@ -59,7 +62,7 @@ export default {
computed: { computed: {
isButtonDisabled() { isButtonDisabled() {
return this.$v.name.$invalid; return this.v$.name.$invalid;
}, },
}, },
@@ -75,8 +78,8 @@ export default {
this.$emit('close'); this.$emit('close');
}, },
async saveCustomViews() { async saveCustomViews() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) { if (this.v$.$invalid) {
return; return;
} }
try { try {
@@ -7,7 +7,7 @@
/> />
<form class="w-full" @submit.prevent="onCreate"> <form class="w-full" @submit.prevent="onCreate">
<div class="w-full"> <div class="w-full">
<label :class="{ error: $v.selectedLocale.$error }"> <label :class="{ error: v$.selectedLocale.$error }">
{{ $t('HELP_CENTER.PORTAL.ADD_LOCALE.LOCALE.LABEL') }} {{ $t('HELP_CENTER.PORTAL.ADD_LOCALE.LOCALE.LABEL') }}
<select v-model="selectedLocale"> <select v-model="selectedLocale">
<option <option
@@ -18,7 +18,7 @@
{{ locale.name }}-{{ locale.code }} {{ locale.name }}-{{ locale.code }}
</option> </option>
</select> </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') }} {{ $t('HELP_CENTER.PORTAL.ADD_LOCALE.LOCALE.ERROR') }}
</span> </span>
</label> </label>
@@ -41,9 +41,11 @@
<script> <script>
import Modal from 'dashboard/components/Modal.vue'; import Modal from 'dashboard/components/Modal.vue';
import alertMixin from 'shared/mixins/alertMixin'; 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 allLocales from 'shared/constants/locales.js';
import { PORTALS_EVENTS } from '../../../../helper/AnalyticsHelper/events'; import { PORTALS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import { useVuelidate } from '@vuelidate/core';
export default { export default {
components: { components: {
Modal, Modal,
@@ -59,6 +61,9 @@ export default {
default: () => ({}), default: () => ({}),
}, },
}, },
setup() {
return { v$: useVuelidate() };
},
data() { data() {
return { return {
selectedLocale: '', selectedLocale: '',
@@ -94,8 +99,8 @@ export default {
}, },
methods: { methods: {
async onCreate() { async onCreate() {
this.$v.$touch(); this.v$.$touch();
if (this.$v.$invalid) { if (this.v$.$invalid) {
return; return;
} }
const updatedLocales = this.addedLocales; const updatedLocales = this.addedLocales;
@@ -137,6 +142,7 @@ export default {
input { input {
margin-bottom: 0; margin-bottom: 0;
} }
.message { .message {
margin-top: 0; margin-top: 0;
} }
@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="flex px-4 items-center justify-between w-full h-16 pt-2 sticky top-0 z-50 bg-white dark:bg-slate-900" class="flex px-4 items-center justify-between w-full h-16 pt-2 sticky top-0 z-10 bg-white dark:bg-slate-900"
> >
<div class="flex items-center"> <div class="flex items-center">
<woot-sidemenu-icon /> <woot-sidemenu-icon />
@@ -59,7 +59,7 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import UpgradePage from './UpgradePage'; import UpgradePage from './UpgradePage.vue';
import { frontendURL } from '../../../../helper/URLHelper'; import { frontendURL } from '../../../../helper/URLHelper';
import Sidebar from 'dashboard/components/layout/Sidebar.vue'; import Sidebar from 'dashboard/components/layout/Sidebar.vue';
import { BUS_EVENTS } from 'shared/constants/busEvents'; import { BUS_EVENTS } from 'shared/constants/busEvents';

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