diff --git a/.env.example b/.env.example index 177cb7fc8..55b998f19 100644 --- a/.env.example +++ b/.env.example @@ -180,8 +180,6 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38: ## Sentry # SENTRY_DSN= -## LogRocket -# LOG_ROCKET_PROJECT_ID=xxxxx/some-project # MICROSOFT CLARITY # MS_CLARITY_TOKEN=xxxxxxxxx @@ -251,9 +249,6 @@ AZURE_APP_SECRET= ## OpenAI key # OPENAI_API_KEY= -# Sentiment analysis model file path -SENTIMENT_FILE_PATH= - # Housekeeping/Performance related configurations # Set to true if you want to remove stale contact inboxes # contact_inboxes with no conversation older than 90 days will be removed diff --git a/Gemfile b/Gemfile index 0dac6e95f..f39655a5c 100644 --- a/Gemfile +++ b/Gemfile @@ -113,10 +113,10 @@ gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false gem 'scout_apm', require: false gem 'sentry-rails', '>= 5.14.0', require: false gem 'sentry-ruby', require: false -gem 'sentry-sidekiq', '>= 5.14.0', require: false +gem 'sentry-sidekiq', '>= 5.15.0', require: false ##-- background job processing --## -gem 'sidekiq', '>= 7.2.1' +gem 'sidekiq', '>= 7.2.4' # We want cron jobs gem 'sidekiq-cron', '>= 1.12.0' @@ -175,9 +175,6 @@ gem 'pgvector' # Convert Website HTML to Markdown gem 'reverse_markdown' -# Sentiment analysis -gem 'informers' - ### Gems required only in specific deployment environments ### ############################################################## diff --git a/Gemfile.lock b/Gemfile.lock index 55021a04b..b5ad38993 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -150,8 +150,8 @@ GEM statsd-ruby (~> 1.1) base64 (0.1.1) bcrypt (3.1.20) + bigdecimal (3.1.7) bindex (0.8.1) - blingfire (0.1.8) bootsnap (1.16.0) msgpack (~> 1.2) brakeman (5.4.1) @@ -316,15 +316,15 @@ GEM google-cloud-translate-v3 (0.6.0) gapic-common (>= 0.17.1, < 2.a) google-cloud-errors (~> 1.0) - google-protobuf (3.25.2) - google-protobuf (3.25.2-arm64-darwin) - google-protobuf (3.25.2-x86_64-darwin) - google-protobuf (3.25.2-x86_64-linux) + google-protobuf (3.25.3) + google-protobuf (3.25.3-arm64-darwin) + google-protobuf (3.25.3-x86_64-darwin) + google-protobuf (3.25.3-x86_64-linux) googleapis-common-protos (1.4.0) google-protobuf (~> 3.14) googleapis-common-protos-types (~> 1.2) grpc (~> 1.27) - googleapis-common-protos-types (1.11.0) + googleapis-common-protos-types (1.14.0) google-protobuf (~> 3.18) googleauth (1.5.2) faraday (>= 0.17.3, < 3.a) @@ -335,14 +335,17 @@ GEM signet (>= 0.16, < 2.a) groupdate (6.2.1) activesupport (>= 5.2) - grpc (1.54.3) - google-protobuf (~> 3.21) + grpc (1.62.0) + google-protobuf (~> 3.25) googleapis-common-protos-types (~> 1.0) - grpc (1.54.3-x86_64-darwin) - google-protobuf (~> 3.21) + grpc (1.62.0-arm64-darwin) + google-protobuf (~> 3.25) googleapis-common-protos-types (~> 1.0) - grpc (1.54.3-x86_64-linux) - google-protobuf (~> 3.21) + grpc (1.62.0-x86_64-darwin) + google-protobuf (~> 3.25) + googleapis-common-protos-types (~> 1.0) + grpc (1.62.0-x86_64-linux) + google-protobuf (~> 3.25) googleapis-common-protos-types (~> 1.0) haikunator (1.1.1) hairtrigger (1.0.0) @@ -371,10 +374,6 @@ GEM image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - informers (0.2.0) - blingfire (>= 0.1.7) - numo-narray - onnxruntime (>= 0.5.1) io-console (0.6.0) irb (1.7.2) reline (>= 0.3.6) @@ -500,7 +499,6 @@ GEM racc (~> 1.4) nokogiri (1.16.4-x86_64-linux) racc (~> 1.4) - numo-narray (0.9.2.1) oauth (1.1.0) oauth-tty (~> 1.0, >= 1.0.1) snaky_hash (~> 2.0) @@ -529,14 +527,6 @@ GEM omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - onnxruntime (0.7.6) - ffi - onnxruntime (0.7.6-arm64-darwin) - ffi - onnxruntime (0.7.6-x86_64-darwin) - ffi - onnxruntime (0.7.6-x86_64-linux) - ffi openssl (3.1.0) orm_adapter (0.5.0) os (1.1.4) @@ -614,7 +604,7 @@ GEM ffi (~> 1.0) redis (5.0.6) redis-client (>= 0.9.0) - redis-client (0.19.1) + redis-client (0.22.1) connection_pool redis-namespace (1.10.0) redis (>= 4) @@ -713,18 +703,19 @@ GEM activesupport (>= 4) selectize-rails (0.12.6) semantic_range (3.0.0) - sentry-rails (5.14.0) + sentry-rails (5.17.3) railties (>= 5.0) - sentry-ruby (~> 5.14.0) - sentry-ruby (5.14.0) + sentry-ruby (~> 5.17.3) + sentry-ruby (5.17.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-sidekiq (5.14.0) - sentry-ruby (~> 5.14.0) + sentry-sidekiq (5.17.3) + sentry-ruby (~> 5.17.3) sidekiq (>= 3.0) sexp_processor (4.17.0) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) - sidekiq (7.2.1) + sidekiq (7.2.4) concurrent-ruby (< 2) connection_pool (>= 2.3.0) rack (>= 2.2.4) @@ -892,7 +883,6 @@ DEPENDENCIES hashie html2text! image_processing - informers jbuilder json_refs json_schemer @@ -943,9 +933,9 @@ DEPENDENCIES seed_dump sentry-rails (>= 5.14.0) sentry-ruby - sentry-sidekiq (>= 5.14.0) + sentry-sidekiq (>= 5.15.0) shoulda-matchers - sidekiq (>= 7.2.1) + sidekiq (>= 7.2.4) sidekiq-cron (>= 1.12.0) simplecov (= 0.17.1) slack-ruby-client (~> 2.2.0) diff --git a/app/controllers/public/api/v1/portals_controller.rb b/app/controllers/public/api/v1/portals_controller.rb index fe51ed161..e8fb867cb 100644 --- a/app/controllers/public/api/v1/portals_controller.rb +++ b/app/controllers/public/api/v1/portals_controller.rb @@ -6,6 +6,12 @@ class Public::Api::V1::PortalsController < Public::Api::V1::Portals::BaseControl def show; end + def sitemap + @help_center_url = @portal.custom_domain || ChatwootApp.help_center_root + # if help_center_url does not contain a protocol, prepend it with https + @help_center_url = "https://#{@help_center_url}" unless @help_center_url.include?('://') + end + private def portal diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss index 06dabb7cb..4b33d5b10 100644 --- a/app/javascript/dashboard/assets/scss/_woot.scss +++ b/app/javascript/dashboard/assets/scss/_woot.scss @@ -42,19 +42,20 @@ } @layer base { + // scss-lint:disable PropertySortOrder :root { - --color-amber-100: 255 243 208; - --color-amber-200: 255 236 183; --color-amber-25: 254 253 251; - --color-amber-300: 255 224 161; - --color-amber-400: 245 208 140; --color-amber-50: 255 249 237; - --color-amber-500: 228 187 120; + --color-amber-75: 255 243 208; + --color-amber-100: 255 236 183; + --color-amber-200: 255 224 161; + --color-amber-300: 245 208 140; + --color-amber-400: 228 187 120; + --color-amber-500: 214 163 92; --color-amber-600: 214 163 92; - --color-amber-700: 214 163 92; - --color-amber-800: 255 186 26; - --color-amber-900: 145 89 48; - --color-amber-950: 79 52 34; + --color-amber-700: 255 186 26; + --color-amber-800: 145 89 48; + --color-amber-900: 79 52 34; --color-ash-100: 235 235 239; --color-ash-200: 228 228 233; @@ -69,18 +70,18 @@ --color-ash-800: 96 100 108; --color-ash-900: 28 32 36; - --color-primary-100: 222 234 255; - --color-primary-200: 206 223 255; - --color-primary-25: 252 253 254; - --color-primary-300: 186 209 255; - --color-primary-400: 163 190 255; - --color-primary-50: 246 249 255; - --color-primary-500: 131 164 250; - --color-primary-600: 69 110 255; - --color-primary-700: 57 95 241; - --color-primary-75: 236 242 255; - --color-primary-800: 53 88 217; - --color-primary-900: 27 44 99; + --color-primary-25: 251 253 255; + --color-primary-50: 245 249 255; + --color-primary-75: 233 243 255; + --color-primary-100: 218 236 255; + --color-primary-200: 201 226 255; + --color-primary-300: 181 213 255; + --color-primary-400: 155 195 252; + --color-primary-500: 117 171 247; + --color-primary-600: 39 129 246; + --color-primary-700: 16 115 233; + --color-primary-800: 8 109 224; + --color-primary-900: 11 50 101; --color-ruby-100: 255 220 225; --color-ruby-200: 255 206 214; @@ -107,21 +108,125 @@ --color-teal-75: 236 249 255; --color-teal-800: 0 133 115; --color-teal-900: 13 61 56; + + --color-green-25: 251 254 252; + --color-green-50: 244 251 246; + --color-green-75: 230 246 235; + --color-green-100: 214 241 223; + --color-green-200: 196 232 209; + --color-green-300: 173 221 192; + --color-green-400: 142 206 170; + --color-green-500: 91 185 139; + --color-green-600: 48 164 108; + --color-green-700: 43 154 102; + --color-green-800: 33 131 88; + --color-green-900: 25 59 45; + + --color-mint-25: 249 254 253; + --color-mint-50: 242 251 249; + --color-mint-75: 221 249 242; + --color-mint-100: 200 244 233; + --color-mint-200: 179 236 222; + --color-mint-300: 156 224 208; + --color-mint-400: 126 207 189; + --color-mint-500: 76 187 165; + --color-mint-600: 134 234 212; + --color-mint-700: 125 224 203; + --color-mint-800: 2 120 100; + --color-mint-900: 22 67 60; + + --color-sky-25: 249 254 255; + --color-sky-50: 241 250 253; + --color-sky-75: 225 246 253; + --color-sky-100: 209 240 250; + --color-sky-200: 190 231 245; + --color-sky-300: 169 218 237; + --color-sky-400: 141 202 227; + --color-sky-500: 96 179 215; + --color-sky-600: 124 226 254; + --color-sky-700: 116 218 248; + --color-sky-800: 0 116 158; + --color-sky-900: 29 62 86; + + --color-indigo-25: 253 253 254; + --color-indigo-50: 247 249 255; + --color-indigo-75: 237 242 254; + --color-indigo-100: 225 233 255; + --color-indigo-200: 210 222 255; + --color-indigo-300: 193 208 255; + --color-indigo-400: 171 189 249; + --color-indigo-500: 141 164 239; + --color-indigo-600: 62 99 221; + --color-indigo-700: 51 88 212; + --color-indigo-800: 58 91 199; + --color-indigo-900: 31 45 92; + + --color-iris-25: 253 253 255; + --color-iris-50: 248 248 255; + --color-iris-75: 240 241 254; + --color-iris-100: 230 231 255; + --color-iris-200: 218 220 255; + --color-iris-300: 203 205 255; + --color-iris-400: 184 186 248; + --color-iris-500: 155 158 240; + --color-iris-600: 91 91 214; + --color-iris-700: 81 81 205; + --color-iris-800: 87 83 198; + --color-iris-900: 39 41 98; + + --color-violet-25: 253 252 254; + --color-violet-50: 250 248 255; + --color-violet-75: 244 240 254; + --color-violet-100: 235 228 255; + --color-violet-200: 225 217 255; + --color-violet-300: 212 202 254; + --color-violet-400: 194 181 245; + --color-violet-500: 170 153 236; + --color-violet-600: 110 86 207; + --color-violet-700: 101 77 196; + --color-violet-800: 101 80 185; + --color-violet-900: 47 38 95; + + --color-pink-25: 255 252 254; + --color-pink-50: 254 247 251; + --color-pink-75: 254 233 245; + --color-pink-100: 251 220 239; + --color-pink-200: 246 206 231; + --color-pink-300: 239 191 221; + --color-pink-400: 231 172 208; + --color-pink-500: 221 147 194; + --color-pink-600: 214 64 159; + --color-pink-700: 207 56 151; + --color-pink-800: 194 41 138; + --color-pink-900: 101 18 73; + + --color-orange-25: 254 252 251; + --color-orange-50: 255 247 237; + --color-orange-75: 255 239 214; + --color-orange-100: 255 223 181; + --color-orange-200: 255 209 154; + --color-orange-300: 255 193 130; + --color-orange-400: 245 174 115; + --color-orange-500: 236 148 85; + --color-orange-600: 247 107 21; + --color-orange-700: 239 95 0; + --color-orange-800: 204 78 0; + --color-orange-900: 88 45 29; } // scss-lint:disable QualifyingElement body.dark { + --color-amber-25: 31 19 0; + --color-amber-50: 37 24 4; + --color-amber-75: 48 32 11; --color-amber-100: 57 39 15; --color-amber-200: 67 46 18; - --color-amber-25: 31 19 0; --color-amber-300: 83 57 22; --color-amber-400: 111 77 29; - --color-amber-50: 37 24 4; --color-amber-500: 169 118 42; --color-amber-600: 169 118 42; --color-amber-700: 255 203 71; --color-amber-800: 255 204 77; --color-amber-900: 255 231 179; - --color-amber-950: 255 231 179; --color-ash-100: 46 48 53; --color-ash-200: 53 55 60; @@ -136,18 +241,18 @@ --color-ash-800: 173 177 184; --color-ash-900: 237 238 240; - --color-primary-100: 24 43 109; - --color-primary-200: 31 53 129; - --color-primary-25: 11 16 31; - --color-primary-300: 40 64 146; - --color-primary-400: 48 76 168; - --color-primary-50: 16 23 41; - --color-primary-500: 57 89 196; - --color-primary-600: 69 110 255; - --color-primary-700: 58 96 240; - --color-primary-75: 20 34 81; - --color-primary-800: 140 177 255; - --color-primary-900: 210 225 255; + --color-primary-25: 10 17 28; + --color-primary-50: 15 24 38; + --color-primary-75: 15 39 72; + --color-primary-100: 10 49 99; + --color-primary-200: 18 61 117; + --color-primary-300: 29 74 134; + --color-primary-400: 40 89 156; + --color-primary-500: 48 106 186; + --color-primary-600: 39 129 246; + --color-primary-700: 21 116 231; + --color-primary-800: 126 182 255; + --color-primary-900: 205 227 255; --color-ruby-100: 78 19 37; --color-ruby-200: 94 26 46; @@ -174,5 +279,108 @@ --color-teal-75: 13 45 42; --color-teal-800: 11 216 182; --color-teal-900: 173 240 221; + + --color-green-25: 14 21 18; + --color-green-50: 18 27 23; + --color-green-75: 19 45 33; + --color-green-100: 17 59 41; + --color-green-200: 23 73 51; + --color-green-300: 32 87 62; + --color-green-400: 40 104 74; + --color-green-500: 47 124 87; + --color-green-600: 48 164 108; + --color-green-700: 51 176 116; + --color-green-800: 61 214 140; + --color-green-900: 177 241 203; + + --color-mint-25: 14 21 21; + --color-mint-50: 15 27 27; + --color-mint-75: 9 44 43; + --color-mint-100: 0 58 56; + --color-mint-200: 0 71 68; + --color-mint-300: 16 86 80; + --color-mint-400: 30 104 95; + --color-mint-500: 39 127 112; + --color-mint-600: 134 234 212; + --color-mint-700: 168 245 229; + --color-mint-800: 88 213 186; + --color-mint-900: 196 245 225; + + --color-sky-25: 14 21 21; + --color-sky-50: 15 27 27; + --color-sky-75: 9 44 43; + --color-sky-100: 0 58 56; + --color-sky-200: 0 71 68; + --color-sky-300: 16 86 80; + --color-sky-400: 30 104 95; + --color-sky-500: 39 127 112; + --color-sky-600: 134 234 212; + --color-sky-700: 168 245 229; + --color-sky-800: 88 213 186; + --color-sky-900: 196 245 225; + + --color-indigo-25: 17 19 31; + --color-indigo-50: 20 23 38; + --color-indigo-75: 24 36 73; + --color-indigo-100: 29 46 98; + --color-indigo-200: 37 57 116; + --color-indigo-300: 48 67 132; + --color-indigo-400: 58 79 151; + --color-indigo-500: 67 93 177; + --color-indigo-600: 62 99 221; + --color-indigo-700: 84 114 228; + --color-indigo-800: 158 177 255; + --color-indigo-900: 214 225 255; + + --color-iris-25: 19 19 30; + --color-iris-50: 23 22 37; + --color-iris-75: 32 34 72; + --color-iris-100: 38 42 101; + --color-iris-200: 48 51 116; + --color-iris-300: 61 62 130; + --color-iris-400: 74 74 149; + --color-iris-500: 89 88 177; + --color-iris-600: 91 91 214; + --color-iris-700: 110 106 222; + --color-iris-800: 177 169 255; + --color-iris-900: 224 223 254; + + --color-violet-25: 20 18 31; + --color-violet-50: 27 21 37; + --color-violet-75: 41 31 67; + --color-violet-100: 51 37 91; + --color-violet-200: 60 46 105; + --color-violet-300: 71 56 118; + --color-violet-400: 86 70 139; + --color-violet-500: 105 88 173; + --color-violet-600: 110 86 207; + --color-violet-700: 125 102 217; + --color-violet-800: 186 167 255; + --color-violet-900: 226 221 254; + + --color-pink-25: 25 17 23; + --color-pink-50: 33 18 29; + --color-pink-75: 55 23 47; + --color-pink-100: 75 20 61; + --color-pink-200: 89 28 71; + --color-pink-300: 105 41 85; + --color-pink-400: 131 56 105; + --color-pink-500: 168 72 133; + --color-pink-600: 214 64 159; + --color-pink-700: 222 81 168; + --color-pink-800: 255 141 204; + --color-pink-900: 253 209 234; + --color-orange-25: 23 18 14; + --color-orange-50: 30 22 15; + --color-orange-75: 51 30 11; + --color-orange-100: 70 33 0; + --color-orange-200: 86 40 0; + --color-orange-300: 102 53 12; + --color-orange-400: 126 69 29; + --color-orange-500: 163 88 41; + --color-orange-600: 247 107 21; + --color-orange-700: 255 128 31; + --color-orange-800: 255 160 87; + --color-orange-900: 255 224 194; } } diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 26c057f98..e7fae6e01 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -185,7 +185,7 @@ import ConversationBasicFilter from './widgets/conversation/ConversationBasicFil import ChatTypeTabs from './widgets/ChatTypeTabs.vue'; import ConversationItem from './ConversationItem.vue'; import timeMixin from '../mixins/time'; -import eventListenerMixins from 'shared/mixins/eventListenerMixins'; +import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins'; import conversationMixin from '../mixins/conversations'; import wootConstants from 'dashboard/constants/globals'; import advancedFilterTypes from './widgets/conversation/advancedFilterItems'; @@ -199,11 +199,6 @@ import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages'; import countries from 'shared/constants/countries'; import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper'; - -import { - hasPressedAltAndJKey, - hasPressedAltAndKKey, -} from 'shared/helpers/KeyboardHelpers'; import { conversationListPageURL } from '../helper/URLHelper'; import { isOnMentionsView, @@ -228,7 +223,7 @@ export default { mixins: [ timeMixin, conversationMixin, - eventListenerMixins, + keyboardEventListenerMixins, alertMixin, filterMixin, uiSettingsMixin, @@ -691,30 +686,40 @@ export default { lastConversationIndex, }; }, - handleKeyEvents(e) { - if (hasPressedAltAndJKey(e)) { - const { allConversations, activeConversationIndex } = - this.getKeyboardListenerParams(); - if (activeConversationIndex === -1) { - allConversations[0].click(); - } - if (activeConversationIndex >= 1) { - allConversations[activeConversationIndex - 1].click(); - } + handlePreviousConversation() { + const { allConversations, activeConversationIndex } = + this.getKeyboardListenerParams(); + if (activeConversationIndex === -1) { + allConversations[0].click(); } - if (hasPressedAltAndKKey(e)) { - const { - allConversations, - activeConversationIndex, - lastConversationIndex, - } = this.getKeyboardListenerParams(); - if (activeConversationIndex === -1) { - allConversations[lastConversationIndex].click(); - } else if (activeConversationIndex < lastConversationIndex) { - allConversations[activeConversationIndex + 1].click(); - } + if (activeConversationIndex >= 1) { + allConversations[activeConversationIndex - 1].click(); } }, + handleNextConversation() { + const { + allConversations, + activeConversationIndex, + lastConversationIndex, + } = this.getKeyboardListenerParams(); + if (activeConversationIndex === -1) { + allConversations[lastConversationIndex].click(); + } else if (activeConversationIndex < lastConversationIndex) { + allConversations[activeConversationIndex + 1].click(); + } + }, + getKeyboardEvents() { + return { + 'Alt+KeyJ': { + action: () => this.handlePreviousConversation(), + allowOnFocusedInput: true, + }, + 'Alt+KeyK': { + action: () => this.handleNextConversation(), + allowOnFocusedInput: true, + }, + }; + }, resetAndFetchData() { this.appliedFilter = []; this.resetBulkActions(); diff --git a/app/javascript/dashboard/components/CustomAttribute.vue b/app/javascript/dashboard/components/CustomAttribute.vue index b50ad54cb..2c0fb48f7 100644 --- a/app/javascript/dashboard/components/CustomAttribute.vue +++ b/app/javascript/dashboard/components/CustomAttribute.vue @@ -12,7 +12,7 @@
{{ label }} + diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue index 84e33cc42..61375dc21 100644 --- a/app/javascript/dashboard/components/buttons/ResolveAction.vue +++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue @@ -88,15 +88,9 @@ + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarAction.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarAction.vue new file mode 100644 index 000000000..447a83bf3 --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarAction.vue @@ -0,0 +1,79 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateInput.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateInput.vue new file mode 100644 index 000000000..b9fe88993 --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateInput.vue @@ -0,0 +1,74 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateRange.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateRange.vue new file mode 100644 index 000000000..38d4dd76f --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarDateRange.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarFooter.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarFooter.vue new file mode 100644 index 000000000..828044e1c --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarFooter.vue @@ -0,0 +1,28 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarMonth.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarMonth.vue new file mode 100644 index 000000000..ee68e7341 --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarMonth.vue @@ -0,0 +1,86 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeek.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeek.vue new file mode 100644 index 000000000..c448e3eb1 --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeek.vue @@ -0,0 +1,172 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeekLabel.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeekLabel.vue new file mode 100644 index 000000000..ebfe9a374 --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarWeekLabel.vue @@ -0,0 +1,15 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/CalendarYear.vue b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarYear.vue new file mode 100644 index 000000000..1f5e7224f --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/CalendarYear.vue @@ -0,0 +1,86 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/components/DatePickerButton.vue b/app/javascript/dashboard/components/ui/DatePicker/components/DatePickerButton.vue new file mode 100644 index 000000000..c5ae54b6f --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/components/DatePickerButton.vue @@ -0,0 +1,71 @@ + + + diff --git a/app/javascript/dashboard/components/ui/DatePicker/helpers/DatePickerHelper.js b/app/javascript/dashboard/components/ui/DatePicker/helpers/DatePickerHelper.js new file mode 100644 index 000000000..85e41603f --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/helpers/DatePickerHelper.js @@ -0,0 +1,219 @@ +import { + startOfDay, + subDays, + endOfDay, + subMonths, + addMonths, + subYears, + addYears, + startOfMonth, + isSameMonth, + format, + startOfWeek, + addDays, + eachDayOfInterval, + endOfMonth, + isSameDay, + isWithinInterval, +} from 'date-fns'; + +// Constants for calendar and date ranges +export const calendarWeeks = [ + { id: 1, label: 'M' }, + { id: 2, label: 'T' }, + { id: 3, label: 'W' }, + { id: 4, label: 'T' }, + { id: 5, label: 'F' }, + { id: 6, label: 'S' }, + { id: 7, label: 'S' }, +]; + +export const dateRanges = [ + { label: 'DATE_PICKER.DATE_RANGE_OPTIONS.LAST_7_DAYS', value: 'last7days' }, + { label: 'DATE_PICKER.DATE_RANGE_OPTIONS.LAST_30_DAYS', value: 'last30days' }, + { + label: 'DATE_PICKER.DATE_RANGE_OPTIONS.LAST_3_MONTHS', + value: 'last3months', + }, + { + label: 'DATE_PICKER.DATE_RANGE_OPTIONS.LAST_6_MONTHS', + value: 'last6months', + }, + { label: 'DATE_PICKER.DATE_RANGE_OPTIONS.LAST_YEAR', value: 'lastYear' }, + { label: 'DATE_PICKER.DATE_RANGE_OPTIONS.CUSTOM_RANGE', value: 'custom' }, +]; + +export const DATE_RANGE_TYPES = { + LAST_7_DAYS: 'last7days', + LAST_30_DAYS: 'last30days', + LAST_3_MONTHS: 'last3months', + LAST_6_MONTHS: 'last6months', + LAST_YEAR: 'lastYear', + CUSTOM_RANGE: 'custom', +}; + +export const CALENDAR_TYPES = { + START_CALENDAR: 'start', + END_CALENDAR: 'end', +}; + +export const CALENDAR_PERIODS = { + WEEK: 'week', + MONTH: 'month', + YEAR: 'year', +}; + +// Utility functions for date operations +export const monthName = currentDate => format(currentDate, 'MMMM'); +export const yearName = currentDate => format(currentDate, 'yyyy'); + +export const getIntlDateFormatForLocale = () => { + const year = 2222; + const month = 12; + const day = 15; + const date = new Date(year, month - 1, day); + const formattedDate = new Intl.DateTimeFormat(navigator.language).format( + date + ); + return formattedDate + .replace(`${year}`, 'yyyy') + .replace(`${month}`, 'MM') + .replace(`${day}`, 'dd'); +}; + +// Utility functions for calendar operations +export const chunk = (array, size) => + Array.from({ length: Math.ceil(array.length / size) }, (_, index) => + array.slice(index * size, index * size + size) + ); + +export const getWeeksForMonth = (date, weekStartsOn = 1) => { + const startOfTheMonth = startOfMonth(date); + const startOfTheFirstWeek = startOfWeek(startOfTheMonth, { weekStartsOn }); + const endOfTheLastWeek = addDays(startOfTheFirstWeek, 41); // Covering six weeks to fill the calendar + return chunk( + eachDayOfInterval({ start: startOfTheFirstWeek, end: endOfTheLastWeek }), + 7 + ); +}; + +export const moveCalendarDate = ( + calendar, + startCurrentDate, + endCurrentDate, + direction, + period +) => { + const adjustFunctions = { + month: { prev: subMonths, next: addMonths }, + year: { prev: subYears, next: addYears }, + }; + + const adjust = adjustFunctions[period][direction]; + + if (calendar === 'start') { + const newStart = adjust(startCurrentDate, 1); + return { start: newStart, end: endCurrentDate }; + } + const newEnd = adjust(endCurrentDate, 1); + return { start: startCurrentDate, end: newEnd }; +}; + +// Date comparison functions +export const isToday = (currentDate, date) => + date.getDate() === currentDate.getDate() && + date.getMonth() === currentDate.getMonth() && + date.getFullYear() === currentDate.getFullYear(); + +export const isCurrentMonth = (day, referenceDate) => + isSameMonth(day, referenceDate); + +export const isLastDayOfMonth = day => { + const lastDay = endOfMonth(day); + return day.getDate() === lastDay.getDate(); +}; + +export const dayIsInRange = (date, startDate, endDate) => { + if (!startDate || !endDate) { + return false; + } + // Normalize dates to ignore time differences + let startOfDayStart = startOfDay(startDate); + let startOfDayEnd = startOfDay(endDate); + // Swap if start is greater than end + if (startOfDayStart > startOfDayEnd) { + [startOfDayStart, startOfDayEnd] = [startOfDayEnd, startOfDayStart]; + } + // Check if the date is within the interval or is the same as the start date + return ( + isSameDay(date, startOfDayStart) || + isWithinInterval(date, { + start: startOfDayStart, + end: startOfDayEnd, + }) + ); +}; + +// Handling hovering states in date range pickers +export const isHoveringDayInRange = ( + day, + startDate, + endDate, + hoveredEndDate +) => { + if (endDate && hoveredEndDate && startDate <= hoveredEndDate) { + // Ensure the start date is not after the hovered end date + return isWithinInterval(day, { start: startDate, end: hoveredEndDate }); + } + return false; +}; + +export const isHoveringNextDayInRange = ( + day, + startDate, + endDate, + hoveredEndDate +) => { + if (startDate && !endDate && hoveredEndDate) { + // If a start date is selected, and we're hovering (but haven't clicked an end date yet) + const nextDay = addDays(day, 1); + return isWithinInterval(nextDay, { start: startDate, end: hoveredEndDate }); + } + if (startDate && endDate) { + // Normal range checking between selected start and end dates + const nextDay = addDays(day, 1); + return isWithinInterval(nextDay, { start: startDate, end: endDate }); + } + return false; +}; + +// Helper func to determine active date ranges based on user selection +export const getActiveDateRange = (range, currentDate) => { + const ranges = { + last7days: () => ({ + start: startOfDay(subDays(currentDate, 6)), + end: endOfDay(currentDate), + }), + last30days: () => ({ + start: startOfDay(subDays(currentDate, 29)), + end: endOfDay(currentDate), + }), + last3months: () => ({ + start: startOfDay(subMonths(currentDate, 3)), + end: endOfDay(currentDate), + }), + last6months: () => ({ + start: startOfDay(subMonths(currentDate, 6)), + end: endOfDay(currentDate), + }), + lastYear: () => ({ + start: startOfDay(subMonths(currentDate, 12)), + end: endOfDay(currentDate), + }), + custom: () => ({ start: currentDate, end: currentDate }), + }; + + return ( + ranges[range] || (() => ({ start: currentDate, end: currentDate })) + )(); +}; diff --git a/app/javascript/dashboard/components/ui/DatePicker/helpers/specs/DatePickerHelper.spec.js b/app/javascript/dashboard/components/ui/DatePicker/helpers/specs/DatePickerHelper.spec.js new file mode 100644 index 000000000..3510ee0f3 --- /dev/null +++ b/app/javascript/dashboard/components/ui/DatePicker/helpers/specs/DatePickerHelper.spec.js @@ -0,0 +1,309 @@ +import { + monthName, + yearName, + getIntlDateFormatForLocale, + getWeeksForMonth, + isToday, + isCurrentMonth, + isLastDayOfMonth, + dayIsInRange, + getActiveDateRange, + isHoveringDayInRange, + isHoveringNextDayInRange, + moveCalendarDate, + chunk, +} from '../DatePickerHelper'; + +describe('Date formatting functions', () => { + const testDate = new Date(2020, 4, 15); // May 15, 2020 + + beforeEach(() => { + jest.spyOn(navigator, 'language', 'get').mockReturnValue('en-US'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('returns the correct month name from a date', () => { + expect(monthName(testDate)).toBe('May'); + }); + + it('returns the correct year from a date', () => { + expect(yearName(testDate)).toBe('2020'); + }); + + it('returns the correct date format for the current locale en-US', () => { + const expected = 'MM/dd/yyyy'; + expect(getIntlDateFormatForLocale()).toBe(expected); + }); + + it('returns the correct date format for the current locale en-IN', () => { + jest.spyOn(navigator, 'language', 'get').mockReturnValue('en-IN'); + const expected = 'dd/MM/yyyy'; + expect(getIntlDateFormatForLocale()).toBe(expected); + }); +}); + +describe('chunk', () => { + const array = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + it('correctly chunks an array into smaller arrays of given size', () => { + const expected = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ]; + expect(chunk(array, 3)).toEqual(expected); + }); + + it('handles arrays that do not divide evenly by the chunk size', () => { + const expected = [[1, 2], [3, 4], [5, 6], [7, 8], [9]]; + expect(chunk(array, 2)).toEqual(expected); + }); +}); + +describe('getWeeksForMonth', () => { + it('returns the correct weeks array for a month starting on Monday', () => { + const date = new Date(2020, 3, 1); // April 2020 + const weeks = getWeeksForMonth(date, 1); + expect(weeks.length).toBe(6); + expect(weeks[0][0]).toEqual(new Date(2020, 2, 30)); // Check if first day of the first week is correct + }); +}); + +describe('moveCalendarDate', () => { + it('handles the year transition when moving the start date backward by one month from January', () => { + const startDate = new Date(2021, 0, 1); + const endDate = new Date(2021, 0, 31); + const result = moveCalendarDate( + 'start', + startDate, + endDate, + 'prev', + 'month' + ); + const expectedStartDate = new Date(2020, 11, 1); + expect(result.start.toISOString()).toEqual(expectedStartDate.toISOString()); + expect(result.end.toISOString()).toEqual(endDate.toISOString()); + }); + + it('handles the year transition when moving the start date forward by one month from January', () => { + const startDate = new Date(2020, 0, 1); + const endDate = new Date(2020, 1, 31); + const result = moveCalendarDate( + 'start', + startDate, + endDate, + 'next', + 'month' + ); + const expectedStartDate = new Date(2020, 1, 1); + expect(result.start.toISOString()).toEqual(expectedStartDate.toISOString()); + expect(result.end.toISOString()).toEqual(endDate.toISOString()); + }); + + it('handles the year transition when moving the end date forward by one month from December', () => { + const startDate = new Date(2021, 11, 1); + const endDate = new Date(2021, 11, 31); + const result = moveCalendarDate('end', startDate, endDate, 'next', 'month'); + const expectedEndDate = new Date(2022, 0, 31); + expect(result.start).toEqual(startDate); + expect(result.end.toISOString()).toEqual(expectedEndDate.toISOString()); + }); + it('handles the year transition when moving the end date backward by one month from December', () => { + const startDate = new Date(2021, 11, 1); + const endDate = new Date(2021, 11, 31); + const result = moveCalendarDate('end', startDate, endDate, 'prev', 'month'); + const expectedEndDate = new Date(2021, 10, 30); + + expect(result.start).toEqual(startDate); + expect(result.end.toISOString()).toEqual(expectedEndDate.toISOString()); + }); +}); + +describe('isToday', () => { + it('returns true if the dates are the same', () => { + const today = new Date(); + const alsoToday = new Date(today); + expect(isToday(today, alsoToday)).toBeTruthy(); + }); + + it('returns false if the dates are not the same', () => { + const today = new Date(); + const notToday = new Date( + today.getFullYear(), + today.getMonth(), + today.getDate() - 1 + ); + expect(isToday(today, notToday)).toBeFalsy(); + }); +}); + +describe('isCurrentMonth', () => { + const referenceDate = new Date(2020, 6, 15); // July 15, 2020 + + it('returns true if the day is in the same month as the reference date', () => { + const testDay = new Date(2020, 6, 1); + expect(isCurrentMonth(testDay, referenceDate)).toBeTruthy(); + }); + + it('returns false if the day is not in the same month as the reference date', () => { + const testDay = new Date(2020, 5, 30); + expect(isCurrentMonth(testDay, referenceDate)).toBeFalsy(); + }); +}); + +describe('isLastDayOfMonth', () => { + it('returns true if the day is the last day of the month', () => { + const testDay = new Date(2020, 6, 31); // July 31, 2020 + expect(isLastDayOfMonth(testDay)).toBeTruthy(); + }); + + it('returns false if the day is not the last day of the month', () => { + const testDay = new Date(2020, 6, 30); // July 30, 2020 + expect(isLastDayOfMonth(testDay)).toBeFalsy(); + }); +}); + +describe('dayIsInRange', () => { + it('returns true if the date is within the range', () => { + const start = new Date(2020, 1, 10); + const end = new Date(2020, 1, 20); + const testDate = new Date(2020, 1, 15); + expect(dayIsInRange(testDate, start, end)).toBeTruthy(); + }); + + it('returns true if the date is the same as the start date', () => { + const start = new Date(2020, 1, 10); + const end = new Date(2020, 1, 20); + const testDate = new Date(2020, 1, 10); + expect(dayIsInRange(testDate, start, end)).toBeTruthy(); + }); + + it('returns false if the date is outside the range', () => { + const start = new Date(2020, 1, 10); + const end = new Date(2020, 1, 20); + const testDate = new Date(2020, 1, 9); + expect(dayIsInRange(testDate, start, end)).toBeFalsy(); + }); +}); + +describe('isHoveringDayInRange', () => { + const startDate = new Date(2020, 6, 10); + const endDate = new Date(2020, 6, 20); + const hoveredEndDate = new Date(2020, 6, 15); + + it('returns true if the day is within the start and hovered end dates', () => { + const testDay = new Date(2020, 6, 12); + expect( + isHoveringDayInRange(testDay, startDate, endDate, hoveredEndDate) + ).toBeTruthy(); + }); + + it('returns false if the day is outside the hovered date range', () => { + const testDay = new Date(2020, 6, 16); + expect( + isHoveringDayInRange(testDay, startDate, endDate, hoveredEndDate) + ).toBeFalsy(); + }); +}); + +describe('isHoveringNextDayInRange', () => { + const startDate = new Date(2020, 6, 10); + const hoveredEndDate = new Date(2020, 6, 15); + + it('returns true if the next day after the given day is within the start and hovered end dates', () => { + const testDay = new Date(2020, 6, 14); + expect( + isHoveringNextDayInRange(testDay, startDate, null, hoveredEndDate) + ).toBeTruthy(); + }); + + it('returns false if the next day is outside the start and hovered end dates', () => { + const testDay = new Date(2020, 6, 15); + expect( + isHoveringNextDayInRange(testDay, startDate, null, hoveredEndDate) + ).toBeFalsy(); + }); +}); + +describe('getActiveDateRange', () => { + const currentDate = new Date(2020, 5, 15, 12, 0); // May 15, 2020, at noon + + beforeAll(() => { + // Mocking the current date to ensure consistency in tests + jest.useFakeTimers().setSystemTime(currentDate.getTime()); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('returns the correct range for "last7days"', () => { + const range = getActiveDateRange('last7days', new Date()); + const expectedStart = new Date(2020, 5, 9); + expectedStart.setHours(0, 0, 0, 0); + const expectedEnd = new Date(); + expectedEnd.setHours(23, 59, 59, 999); + + expect(range.start).toEqual(expectedStart); + expect(range.end).toEqual(expectedEnd); + }); + + it('returns the correct range for "last30days"', () => { + const range = getActiveDateRange('last30days', new Date()); + const expectedStart = new Date(2020, 4, 17); + expectedStart.setHours(0, 0, 0, 0); + const expectedEnd = new Date(); + expectedEnd.setHours(23, 59, 59, 999); + + expect(range.start).toEqual(expectedStart); + expect(range.end).toEqual(expectedEnd); + }); + + it('returns the correct range for "last3months"', () => { + const range = getActiveDateRange('last3months', new Date()); + const expectedStart = new Date(2020, 2, 15); + expectedStart.setHours(0, 0, 0, 0); + const expectedEnd = new Date(); + expectedEnd.setHours(23, 59, 59, 999); + + expect(range.start).toEqual(expectedStart); + expect(range.end).toEqual(expectedEnd); + }); + + it('returns the correct range for "last6months"', () => { + const range = getActiveDateRange('last6months', new Date()); + const expectedStart = new Date(2019, 11, 15); + expectedStart.setHours(0, 0, 0, 0); + const expectedEnd = new Date(); + expectedEnd.setHours(23, 59, 59, 999); + + expect(range.start).toEqual(expectedStart); + expect(range.end).toEqual(expectedEnd); + }); + + it('returns the correct range for "lastYear"', () => { + const range = getActiveDateRange('lastYear', new Date()); + const expectedStart = new Date(2019, 5, 15); + expectedStart.setHours(0, 0, 0, 0); + const expectedEnd = new Date(); + expectedEnd.setHours(23, 59, 59, 999); + + expect(range.start).toEqual(expectedStart); + expect(range.end).toEqual(expectedEnd); + }); + + it('returns the correct range for "custom date range"', () => { + const range = getActiveDateRange('custom', new Date()); + expect(range.start).toEqual(new Date(currentDate)); + expect(range.end).toEqual(new Date(currentDate)); + }); + + it('handles an unknown range label gracefully', () => { + const range = getActiveDateRange('unknown', new Date()); + expect(range.start).toEqual(new Date(currentDate)); + expect(range.end).toEqual(new Date(currentDate)); + }); +}); diff --git a/app/javascript/dashboard/components/ui/HelperTextPopup.vue b/app/javascript/dashboard/components/ui/HelperTextPopup.vue new file mode 100644 index 000000000..fb14692f6 --- /dev/null +++ b/app/javascript/dashboard/components/ui/HelperTextPopup.vue @@ -0,0 +1,24 @@ + + diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue b/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue index 2d7951b05..a64743547 100644 --- a/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue +++ b/app/javascript/dashboard/components/widgets/AIAssistanceButton.vue @@ -40,8 +40,7 @@ import AIAssistanceModal from './AIAssistanceModal.vue'; import adminMixin from 'dashboard/mixins/aiMixin'; import aiMixin from 'dashboard/mixins/isAdmin'; import { CMD_AI_ASSIST } from 'dashboard/routes/dashboard/commands/commandBarBusEvents'; -import eventListenerMixins from 'shared/mixins/eventListenerMixins'; -import { buildHotKeys } from 'shared/helpers/KeyboardHelpers'; +import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins'; import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue'; @@ -51,7 +50,7 @@ export default { AICTAModal, AIAssistanceCTAButton, }, - mixins: [aiMixin, eventListenerMixins, adminMixin, uiSettingsMixin], + mixins: [aiMixin, keyboardEventListenerMixins, adminMixin, uiSettingsMixin], data: () => ({ showAIAssistanceModal: false, showAICtaModal: false, @@ -87,14 +86,17 @@ export default { }, methods: { - onKeyDownHandler(event) { - const keyPattern = buildHotKeys(event); - const shouldRevertTheContent = - ['meta+z', 'ctrl+z'].includes(keyPattern) && !!this.initialMessage; - if (shouldRevertTheContent) { - this.$emit('replace-text', this.initialMessage); - this.initialMessage = ''; - } + getKeyboardEvents() { + return { + '$mod+KeyZ': { + action: () => { + if (this.initialMessage) { + this.$emit('replace-text', this.initialMessage); + this.initialMessage = ''; + } + }, + }, + }; }, hideAIAssistanceModal() { this.recordAnalytics('DISMISS_AI_SUGGESTION', { diff --git a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue index f73474977..32af9e499 100644 --- a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue +++ b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue @@ -1,17 +1,17 @@ - - diff --git a/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue b/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue index 64db13f5c..86e80748d 100644 --- a/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue +++ b/app/javascript/dashboard/components/widgets/ChatTypeTabs.vue @@ -10,11 +10,10 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue index 47a1c59b5..3de2a6f52 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue @@ -47,13 +47,11 @@ - + +function onFileChange({ file }) { + if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) { + uploadLogoToStorage(file); + } else { + const errorKey = + 'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SIZE_ERROR'; + useAlert(t(errorKey, { size: MAXIMUM_FILE_UPLOAD_SIZE })); + } +} + + + diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue index c00ab9beb..7572f5b80 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue @@ -1,68 +1,140 @@ + + - diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/NameEmojiInput.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/NameEmojiInput.vue index 5d6862758..b888a5440 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/NameEmojiInput.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/NameEmojiInput.vue @@ -36,13 +36,10 @@ + - - diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalCustomization.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalCustomization.vue index d1c9341f1..902dc3ca6 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalCustomization.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalCustomization.vue @@ -1,3 +1,48 @@ + + - - diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue index 3c1c4c872..e6cee06a4 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue @@ -1,6 +1,117 @@ + + - - - diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/NewPortal.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/NewPortal.vue index e14723476..0222044bc 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/NewPortal.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/NewPortal.vue @@ -1,5 +1,5 @@ + - - diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalSettingsFinish.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalSettingsFinish.vue index 690f058a4..3a3e77142 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalSettingsFinish.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalSettingsFinish.vue @@ -1,6 +1,6 @@ - diff --git a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue index 35411f546..556a22de0 100644 --- a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue @@ -57,7 +57,6 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/AudioAlertEvent.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/AudioAlertEvent.vue new file mode 100644 index 000000000..ef4b95a36 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/AudioAlertEvent.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/AudioAlertTone.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/AudioAlertTone.vue new file mode 100644 index 000000000..3246e12af --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/AudioAlertTone.vue @@ -0,0 +1,56 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/AudioNotifications.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/AudioNotifications.vue new file mode 100644 index 000000000..84f369f5a --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/AudioNotifications.vue @@ -0,0 +1,128 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/ChangePassword.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/ChangePassword.vue new file mode 100644 index 000000000..0c2fa9e78 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/ChangePassword.vue @@ -0,0 +1,137 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/HotKeyCard.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/HotKeyCard.vue new file mode 100644 index 000000000..35cbbeb7b --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/HotKeyCard.vue @@ -0,0 +1,64 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/Index.vue new file mode 100644 index 000000000..ce3eb5846 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/Index.vue @@ -0,0 +1,245 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/MessageSignature.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/MessageSignature.vue new file mode 100644 index 000000000..7639465be --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/MessageSignature.vue @@ -0,0 +1,50 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/NotificationCheckBox.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/NotificationCheckBox.vue new file mode 100644 index 000000000..feb180014 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/NotificationCheckBox.vue @@ -0,0 +1,53 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/NotificationPreferences.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/NotificationPreferences.vue new file mode 100644 index 000000000..a2c4555a7 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/NotificationPreferences.vue @@ -0,0 +1,284 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/UserBasicDetails.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/UserBasicDetails.vue new file mode 100644 index 000000000..1a0fb2eae --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/UserBasicDetails.vue @@ -0,0 +1,135 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/UserProfilePicture.vue b/app/javascript/dashboard/routes/dashboard/settings/personal/UserProfilePicture.vue new file mode 100644 index 000000000..d4ab08bc1 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/UserProfilePicture.vue @@ -0,0 +1,40 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/constants.js b/app/javascript/dashboard/routes/dashboard/settings/personal/constants.js new file mode 100644 index 000000000..e25d5b52a --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/constants.js @@ -0,0 +1,52 @@ +export const NOTIFICATION_TYPES = [ + { + label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.CONVERSATION_CREATED', + value: 'conversation_creation', + }, + { + label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.CONVERSATION_ASSIGNED', + value: 'conversation_assignment', + }, + { + label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.CONVERSATION_MENTION', + value: 'conversation_mention', + }, + { + label: + 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.ASSIGNED_CONVERSATION_NEW_MESSAGE', + value: 'assigned_conversation_new_message', + }, + { + label: + 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.PARTICIPATING_CONVERSATION_NEW_MESSAGE', + value: 'participating_conversation_new_message', + }, + { + label: + 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.SLA_MISSED_FIRST_RESPONSE', + value: 'sla_missed_first_response', + }, + { + label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.SLA_MISSED_NEXT_RESPONSE', + value: 'sla_missed_next_response', + }, + { + label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.SLA_MISSED_RESOLUTION', + value: 'sla_missed_resolution', + }, +]; + +export const ALERT_EVENTS = [ + { + value: 'none', + label: 'none', + }, + { + value: 'mine', + label: 'mine', + }, + { + value: 'all', + label: 'all', + }, +]; diff --git a/app/javascript/dashboard/routes/dashboard/settings/personal/personal.routes.js b/app/javascript/dashboard/routes/dashboard/settings/personal/personal.routes.js new file mode 100644 index 000000000..66aaf0a11 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/personal/personal.routes.js @@ -0,0 +1,20 @@ +import { frontendURL } from 'dashboard/helper/URLHelper'; + +const Index = () => import('./Index.vue'); + +export default { + routes: [ + { + path: frontendURL('accounts/:accountId/personal'), + name: 'personal_settings', + roles: ['administrator', 'agent'], + component: Index, + props: { + headerTitle: 'PROFILE_SETTINGS.TITLE', + icon: 'edit', + showNewButton: false, + showSidemenuIcon: false, + }, + }, + ], +}; diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Heatmap.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Heatmap.vue index fb7e7b5eb..6f28dabb5 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Heatmap.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Heatmap.vue @@ -1,308 +1,173 @@ + + - - - diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue index 123a921fc..16fbbcf40 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue @@ -1,24 +1,10 @@ diff --git a/app/javascript/v3/components/Form/CheckBox.vue b/app/javascript/v3/components/Form/CheckBox.vue new file mode 100644 index 000000000..a047be63a --- /dev/null +++ b/app/javascript/v3/components/Form/CheckBox.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/javascript/v3/components/Form/InitialsAvatar.vue b/app/javascript/v3/components/Form/InitialsAvatar.vue new file mode 100644 index 000000000..2a489ee76 --- /dev/null +++ b/app/javascript/v3/components/Form/InitialsAvatar.vue @@ -0,0 +1,43 @@ + + + diff --git a/app/javascript/v3/components/Form/ProfileAvatar.vue b/app/javascript/v3/components/Form/ProfileAvatar.vue new file mode 100644 index 000000000..fb802d35d --- /dev/null +++ b/app/javascript/v3/components/Form/ProfileAvatar.vue @@ -0,0 +1,84 @@ + + diff --git a/app/javascript/v3/components/Form/Select.vue b/app/javascript/v3/components/Form/Select.vue index a7165b7f2..67a311b04 100644 --- a/app/javascript/v3/components/Form/Select.vue +++ b/app/javascript/v3/components/Form/Select.vue @@ -11,11 +11,11 @@ :selected="value" :name="name" :class="{ - 'text-slate-400': !value, - 'text-slate-900 dark:text-slate-100': value, + 'text-ash-400': !value, + 'text-ash-900': value, 'pl-9': icon, }" - class="block w-full px-3 py-2 pr-6 mb-0 border-0 rounded-md shadow-sm outline-none appearance-none select-caret ring-1 ring-inset placeholder:text-slate-400 focus:ring-2 focus:ring-inset focus:ring-woot-500 sm:text-sm sm:leading-6 dark:bg-slate-700 dark:ring-slate-600 dark:focus:ring-woot-500 ring-slate-200" + class="block w-full px-3 py-2 pr-6 mb-0 border-0 shadow-sm outline-none appearance-none rounded-xl select-caret ring-ash-200 ring-1 ring-inset placeholder:text-ash-900 focus:ring-2 focus:ring-inset focus:ring-primary-500 sm:text-sm sm:leading-6" @input="onInput" >