Date: Wed, 18 Feb 2026 22:10:06 -0800
Subject: [PATCH 002/113] feat(cloud-billing): cancel subscriptions at period
end on deletion mark (#13580)
## How to reproduce
In Chatwoot Cloud, mark an account for deletion from account settings
while the account has an active Stripe subscription. Before this change,
deletion marking did not explicitly mark subscriptions to stop renewing
at period end.
## What changed
This PR adds `Enterprise::Billing::CancelCloudSubscriptionsService` and
calls it from the delete action path in
`Enterprise::Api::V1::AccountsController`. The service lists only active
Stripe subscriptions for the customer and sets `cancel_at_period_end:
true` when needed. The account deletion schedule remains unchanged
(existing static 7-day behavior), and Stripe deleted-event fallback
behavior remains unchanged.
## How this was tested
Added and updated specs:
-
`spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb`
-
`spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb`
Executed:
- `bundle exec rspec
spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb`
- `bundle exec rspec
spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb:363`
---
.../enterprise/api/v1/accounts_controller.rb | 8 +++
.../cancel_cloud_subscriptions_service.rb | 24 +++++++++
.../api/v1/accounts_controller_spec.rb | 24 ++++++++-
...cancel_cloud_subscriptions_service_spec.rb | 51 +++++++++++++++++++
4 files changed, 106 insertions(+), 1 deletion(-)
create mode 100644 enterprise/app/services/enterprise/billing/cancel_cloud_subscriptions_service.rb
create mode 100644 spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb
diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb
index 0f829b973..d176db597 100644
--- a/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb
+++ b/enterprise/app/controllers/enterprise/api/v1/accounts_controller.rb
@@ -102,6 +102,8 @@ class Enterprise::Api::V1::AccountsController < Api::BaseController
reason = 'manual_deletion'
if @account.mark_for_deletion(reason)
+ cancel_cloud_subscriptions_for_deletion
+
render json: { message: 'Account marked for deletion' }, status: :ok
else
render json: { message: @account.errors.full_messages.join(', ') }, status: :unprocessable_entity
@@ -125,6 +127,12 @@ class Enterprise::Api::V1::AccountsController < Api::BaseController
render_redirect_url(session.url)
end
+ def cancel_cloud_subscriptions_for_deletion
+ Enterprise::Billing::CancelCloudSubscriptionsService.new(account: @account).perform
+ rescue Stripe::StripeError => e
+ Rails.logger.warn("Failed to cancel cloud subscriptions for account #{@account.id}: #{e.class} - #{e.message}")
+ end
+
def render_redirect_url(redirect_url)
render json: { redirect_url: redirect_url }
end
diff --git a/enterprise/app/services/enterprise/billing/cancel_cloud_subscriptions_service.rb b/enterprise/app/services/enterprise/billing/cancel_cloud_subscriptions_service.rb
new file mode 100644
index 000000000..53032c8ca
--- /dev/null
+++ b/enterprise/app/services/enterprise/billing/cancel_cloud_subscriptions_service.rb
@@ -0,0 +1,24 @@
+class Enterprise::Billing::CancelCloudSubscriptionsService
+ pattr_initialize [:account!]
+
+ def perform
+ return if stripe_customer_id.blank?
+ return unless ChatwootApp.chatwoot_cloud?
+
+ subscriptions.each do |subscription|
+ next if subscription.cancel_at_period_end
+
+ Stripe::Subscription.update(subscription.id, cancel_at_period_end: true)
+ end
+ end
+
+ private
+
+ def subscriptions
+ Stripe::Subscription.list(customer: stripe_customer_id, status: 'active', limit: 100).data
+ end
+
+ def stripe_customer_id
+ account.custom_attributes['stripe_customer_id']
+ end
+end
diff --git a/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb b/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb
index e79cbd237..9089b089a 100644
--- a/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb
+++ b/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb
@@ -357,10 +357,32 @@ RSpec.describe 'Enterprise Billing APIs', type: :request do
context 'when it is an admin' do
before do
# Create the installation config for cloud environment
- InstallationConfig.where(name: 'DEPLOYMENT_ENV').first_or_create(value: 'cloud')
+ InstallationConfig.where(name: 'DEPLOYMENT_ENV').first_or_initialize.update!(value: 'cloud')
end
it 'marks the account for deletion when action is delete' do
+ cancellation_service = instance_double(Enterprise::Billing::CancelCloudSubscriptionsService, perform: true)
+ allow(Enterprise::Billing::CancelCloudSubscriptionsService).to receive(:new).with(account: account)
+ .and_return(cancellation_service)
+
+ post "/enterprise/api/v1/accounts/#{account.id}/toggle_deletion",
+ headers: admin.create_new_auth_token,
+ params: { action_type: 'delete' },
+ as: :json
+
+ expect(response).to have_http_status(:ok)
+ expect(account.reload.custom_attributes['marked_for_deletion_at']).to be_present
+ expect(account.custom_attributes['marked_for_deletion_reason']).to eq('manual_deletion')
+ expect(Enterprise::Billing::CancelCloudSubscriptionsService).to have_received(:new).with(account: account)
+ expect(cancellation_service).to have_received(:perform)
+ end
+
+ it 'returns success even if stripe cancellation fails' do
+ cancellation_service = instance_double(Enterprise::Billing::CancelCloudSubscriptionsService)
+ allow(Enterprise::Billing::CancelCloudSubscriptionsService).to receive(:new).with(account: account)
+ .and_return(cancellation_service)
+ allow(cancellation_service).to receive(:perform).and_raise(Stripe::APIError.new('stripe unavailable'))
+
post "/enterprise/api/v1/accounts/#{account.id}/toggle_deletion",
headers: admin.create_new_auth_token,
params: { action_type: 'delete' },
diff --git a/spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb b/spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb
new file mode 100644
index 000000000..f9eab281b
--- /dev/null
+++ b/spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb
@@ -0,0 +1,51 @@
+require 'rails_helper'
+
+RSpec.describe Enterprise::Billing::CancelCloudSubscriptionsService do
+ subject(:service) { described_class.new(account: account) }
+
+ let(:account) { create(:account, custom_attributes: custom_attributes) }
+ let(:custom_attributes) { { 'stripe_customer_id' => 'cus_123' } }
+
+ describe '#perform' do
+ context 'when deployment is not cloud' do
+ it 'does not call stripe subscriptions api' do
+ allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(false)
+ allow(Stripe::Subscription).to receive(:list)
+
+ service.perform
+
+ expect(Stripe::Subscription).not_to have_received(:list)
+ end
+ end
+
+ context 'when stripe customer id is missing' do
+ let(:custom_attributes) { {} }
+
+ it 'does not call stripe subscriptions api' do
+ allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true)
+ allow(Stripe::Subscription).to receive(:list)
+
+ service.perform
+
+ expect(Stripe::Subscription).not_to have_received(:list)
+ end
+ end
+
+ context 'when account is cloud with active subscriptions' do
+ let(:subscription_response) { Struct.new(:data).new([sub_1, sub_2]) }
+ let(:sub_1) { instance_double(Stripe::Subscription, id: 'sub_1', cancel_at_period_end: false) }
+ let(:sub_2) { instance_double(Stripe::Subscription, id: 'sub_2', cancel_at_period_end: true) }
+
+ it 'marks only active subscriptions that are not yet set to cancel at period end' do
+ allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true)
+ allow(Stripe::Subscription).to receive(:list).and_return(subscription_response)
+ allow(Stripe::Subscription).to receive(:update)
+
+ service.perform
+
+ expect(Stripe::Subscription).to have_received(:list).with(customer: 'cus_123', status: 'active', limit: 100)
+ expect(Stripe::Subscription).to have_received(:update).with('sub_1', cancel_at_period_end: true).once
+ end
+ end
+ end
+end
From c9619eaed2c03509491525385e1771dca673d294 Mon Sep 17 00:00:00 2001
From: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
Date: Thu, 19 Feb 2026 13:55:15 +0530
Subject: [PATCH 003/113] chore: ignore .claude directory in gitignore (#13584)
# Pull Request Template
adds .claude to gitignore
Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com>
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index dbdd35bcd..017c5c224 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,6 +95,7 @@ yarn-debug.log*
.claude/settings.local.json
.cursor
.codex/
+.claude/
CLAUDE.local.md
# Histoire deployment
From 7b2b3ac37d5e6bae7c37c0c7c8cf94cd8ef4d3b0 Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Thu, 19 Feb 2026 15:04:40 +0530
Subject: [PATCH 004/113] feat(V5): Update settings pages UI (#13396)
# Pull Request Template
## Description
This PR updates settings page UI
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---
.../dashboard/assets/scss/_base.scss | 2 +-
.../dashboard/assets/scss/_woot.scss | 80 ++
.../AgentCapacityPolicyCard.vue | 4 +-
.../AssignmentCard/AssignmentCard.vue | 6 +-
.../AssignmentPolicyCard.vue | 32 +-
.../components/ExclusionRules.vue | 2 +-
.../AssignmentPolicy/components/RadioCard.vue | 60 +-
.../Campaigns/CampaignLayout.vue | 10 +-
.../dashboard/components-next/CardLayout.vue | 2 +-
.../CompaniesHeader/CompanyHeader.vue | 6 +-
.../Companies/CompaniesListLayout.vue | 7 +-
.../Contacts/ContactLabels/ContactLabels.vue | 4 +-
.../Contacts/ContactsHeader/ContactHeader.vue | 4 +-
.../ContactsActiveFiltersPreview.vue | 2 +-
.../Contacts/ContactsListLayout.vue | 7 +-
.../AttributeListItem.vue | 91 +-
.../ConversationRequiredAttributeItem.vue | 8 +-
.../ConversationRequiredAttributes.vue | 4 +-
.../CustomAttributes/AttributeBadge.vue | 16 +-
.../components-next/EmptyStateLayout.vue | 2 +-
.../HelpCenter/HelpCenterLayout.vue | 11 +-
.../Pages/LocalePage/AddLocaleDialog.vue | 1 +
.../specs/composeConversationHelper.spec.js | 2 +-
.../Settings/SettingsAccordion.vue | 31 +
.../Settings/SettingsFieldSection.vue | 37 +
.../Settings/SettingsToggleSection.vue | 45 +
.../components-next/avatar/Avatar.vue | 22 +-
.../components-next/captain/PageLayout.vue | 11 +-
.../captain/pageComponents/Paywall.vue | 2 +-
.../combobox/ComboBoxDropdown.vue | 4 +-
.../FeatureSpotlightPopover.vue | 2 +-
.../components-next/icon/provider.js | 4 +-
.../icon/specs/provider.spec.js | 4 +-
.../dashboard/components-next/input/Input.vue | 4 +-
.../{Label => label}/AddLabel.vue | 0
.../dashboard/components-next/label/Label.vue | 71 ++
.../{Label => label}/LabelItem.vue | 0
.../{Label => label}/story/AddLabel.story.vue | 0
.../{Label => label}/story/Label.story.vue | 0
.../{Label => label}/story/fixtures.js | 0
.../pagination/PaginationFooter.vue | 12 +-
.../components-next/select/Select.vue | 97 ++
.../components-next/sidebar/ChannelLeaf.vue | 4 +-
.../components-next/sidebar/Sidebar.vue | 33 +-
.../sidebar/SidebarGroupLeaf.vue | 6 +-
.../components-next/switch/Switch.vue | 16 +-
.../components-next/table/BaseTable.story.vue | 175 +++
.../components-next/table/BaseTable.vue | 60 +
.../components-next/table/BaseTableCell.vue | 22 +
.../components-next/table/BaseTableRow.vue | 14 +
.../dashboard/components-next/table/index.js | 3 +
.../year-in-review/ShareModal.vue | 2 +-
.../dashboard/components/FormSection.vue | 31 -
.../components/widgets/InboxName.vue | 9 +-
.../components/widgets/LoadingState.vue | 2 +-
.../components/widgets/SettingIntroBanner.vue | 2 +-
.../components/widgets/forms/Input.vue | 6 +-
app/javascript/dashboard/helper/inbox.js | 22 +-
.../dashboard/helper/specs/inbox.spec.js | 8 +-
.../dashboard/i18n/locale/en/agentBots.json | 6 +-
.../dashboard/i18n/locale/en/agentMgmt.json | 3 +
.../i18n/locale/en/attributesMgmt.json | 3 +
.../dashboard/i18n/locale/en/automation.json | 9 +-
.../dashboard/i18n/locale/en/cannedMgmt.json | 3 +
.../dashboard/i18n/locale/en/customRole.json | 3 +
.../dashboard/i18n/locale/en/inboxMgmt.json | 21 +-
.../i18n/locale/en/integrationApps.json | 4 +
.../i18n/locale/en/integrations.json | 18 +-
.../dashboard/i18n/locale/en/labelsMgmt.json | 6 +-
.../dashboard/i18n/locale/en/macros.json | 6 +-
.../dashboard/i18n/locale/en/mfa.json | 2 +-
.../dashboard/i18n/locale/en/sla.json | 13 +-
.../i18n/locale/en/teamsSettings.json | 7 +-
.../widget-preview/components/Widget.vue | 489 +++++---
.../pages/CampaignsPageRouteView.vue | 2 +-
.../companies/pages/CompaniesIndex.vue | 2 +-
.../contacts/pages/ContactsIndex.vue | 2 +-
.../inbox/components/InboxListHeader.vue | 12 +-
.../routes/dashboard/notifications/routes.js | 7 +-
.../dashboard/settings/SettingsHeader.vue | 4 +-
.../dashboard/settings/SettingsLayout.vue | 2 +-
.../settings/SettingsSubPageHeader.vue | 6 +-
.../dashboard/settings/SettingsWrapper.vue | 12 +-
.../routes/dashboard/settings/Wrapper.vue | 37 +-
.../dashboard/settings/account/Index.vue | 3 +-
.../account/components/AutoResolve.vue | 4 +-
.../account/components/SectionLayout.vue | 17 +-
.../dashboard/settings/agentBots/Index.vue | 154 ++-
.../dashboard/settings/agents/Index.vue | 202 +--
.../settings/assignmentPolicy/Index.vue | 2 +-
.../pages/AgentAssignmentCreatePage.vue | 4 +-
.../pages/AgentAssignmentEditPage.vue | 7 +-
.../pages/AgentAssignmentIndexPage.vue | 4 +-
.../pages/AgentCapacityCreatePage.vue | 4 +-
.../pages/AgentCapacityEditPage.vue | 7 +-
.../pages/AgentCapacityIndexPage.vue | 4 +-
.../components/AgentAssignmentPolicyForm.vue | 11 +-
.../dashboard/settings/attributes/Index.vue | 50 +-
.../dashboard/settings/auditlogs/Index.vue | 121 +-
.../settings/automation/AutomationRuleRow.vue | 98 +-
.../dashboard/settings/automation/Index.vue | 45 +-
.../dashboard/settings/canned/Index.vue | 211 ++--
.../components/BaseSettingsHeader.vue | 113 +-
.../settings/conversationWorkflow/index.vue | 2 +-
.../dashboard/settings/customRoles/Index.vue | 57 +-
.../component/CustomRolePaywall.vue | 2 +-
.../component/CustomRoleTableBody.vue | 85 +-
.../dashboard/settings/inbox/ChannelList.vue | 2 +-
.../dashboard/settings/inbox/ImapSettings.vue | 134 +-
.../settings/inbox/InboxChannels.vue | 2 +-
.../routes/dashboard/settings/inbox/Index.vue | 152 +--
.../inbox/PreChatForm/PreChatFields.vue | 117 +-
.../settings/inbox/PreChatForm/Settings.vue | 264 ++--
.../dashboard/settings/inbox/Settings.vue | 1078 ++++++++++-------
.../dashboard/settings/inbox/SmtpSettings.vue | 176 +--
.../settings/inbox/WidgetBuilder.vue | 443 -------
.../inbox/channels/google/Reauthorize.vue | 5 +-
.../inbox/channels/instagram/Reauthorize.vue | 5 +-
.../inbox/channels/microsoft/Reauthorize.vue | 5 +-
.../inbox/channels/tiktok/Reauthorize.vue | 5 +-
.../inbox/channels/whatsapp/Reauthorize.vue | 2 +-
.../inbox/components/AccountHealth.vue | 24 +-
.../inbox/components/BotConfiguration.vue | 93 +-
.../settings/inbox/components/BusinessDay.vue | 161 ++-
.../components/SenderNameExamplePreview.vue | 176 ++-
.../inbox/components/WeeklyAvailability.vue | 173 +--
.../settings/inbox/facebook/Reauthorize.vue | 2 +-
.../dashboard/settings/inbox/inbox.routes.js | 4 +-
.../inbox/settingsPage/CollaboratorsPage.vue | 309 ++---
.../inbox/settingsPage/ConfigurationPage.vue | 265 ++--
.../settingsPage/CustomerSatisfactionPage.vue | 329 +++--
.../DashboardApps/DashboardAppsRow.vue | 81 +-
.../integrations/DashboardApps/Index.vue | 107 +-
.../dashboard/settings/integrations/Index.vue | 25 +-
.../settings/integrations/Integration.vue | 6 +-
.../integrations/IntegrationHooks.vue | 55 +-
.../settings/integrations/IntegrationItem.vue | 30 +-
.../settings/integrations/Linear.vue | 22 +-
.../integrations/MultipleIntegrationHooks.vue | 211 ++--
.../settings/integrations/Notion.vue | 22 +-
.../settings/integrations/Shopify.vue | 124 +-
.../integrations/SingleIntegrationHooks.vue | 6 +-
.../dashboard/settings/integrations/Slack.vue | 67 +-
.../Slack/SelectChannelWarning.vue | 6 +-
.../Slack/SlackIntegrationHelpText.vue | 9 +-
.../settings/integrations/Webhooks/Index.vue | 47 +-
.../integrations/Webhooks/WebhookRow.vue | 83 +-
.../integrations/integrations.routes.js | 24 +-
.../dashboard/settings/labels/Index.vue | 133 +-
.../dashboard/settings/macros/Index.vue | 49 +-
.../dashboard/settings/macros/MacroEditor.vue | 2 +-
.../dashboard/settings/macros/MacroForm.vue | 6 +-
.../settings/macros/MacroProperties.vue | 63 +-
.../settings/macros/MacrosTableRow.vue | 105 +-
.../settings/profile/AudioAlertCondition.vue | 6 +-
.../dashboard/settings/profile/HotKeyCard.vue | 8 +-
.../dashboard/settings/profile/Index.vue | 118 +-
.../settings/profile/MfaSettings.vue | 21 +-
.../settings/profile/MfaSettingsCard.vue | 4 +-
.../profile/NotificationPreferences.vue | 18 +-
.../settings/profile/profile.routes.js | 4 +-
.../reports/components/ReportHeader.vue | 13 +-
.../reports/components/ReportsWrapper.vue | 2 +-
.../dashboard/settings/security/Index.vue | 5 +-
.../security/components/SamlPaywall.vue | 2 +-
.../security/components/SamlSettings.vue | 1 +
.../routes/dashboard/settings/sla/Index.vue | 212 +++-
.../{components => }/SLAPaywallEnterprise.vue | 5 +-
.../sla/components/BaseEmptyState.vue | 33 -
.../sla/components/SLABusinessHoursLabel.vue | 32 -
.../settings/sla/components/SLAEmptyState.vue | 21 -
.../settings/sla/components/SLAHeader.vue | 30 -
.../settings/sla/components/SLAListItem.vue | 71 --
.../sla/components/SLAListItemLoading.vue | 41 -
.../sla/components/SLAResponseTime.vue | 37 -
.../settings/teams/AgentSelector.vue | 253 ++--
.../dashboard/settings/teams/Create/Index.vue | 4 +-
.../settings/teams/Edit/EditAgents.vue | 6 +-
.../dashboard/settings/teams/Edit/Index.vue | 4 +-
.../routes/dashboard/settings/teams/Index.vue | 166 ++-
.../shared/components/GreetingsEditor.vue | 8 +-
theme/icons.js | 140 ++-
182 files changed, 5187 insertions(+), 4297 deletions(-)
create mode 100644 app/javascript/dashboard/components-next/Settings/SettingsAccordion.vue
create mode 100644 app/javascript/dashboard/components-next/Settings/SettingsFieldSection.vue
create mode 100644 app/javascript/dashboard/components-next/Settings/SettingsToggleSection.vue
rename app/javascript/dashboard/components-next/{Label => label}/AddLabel.vue (100%)
create mode 100644 app/javascript/dashboard/components-next/label/Label.vue
rename app/javascript/dashboard/components-next/{Label => label}/LabelItem.vue (100%)
rename app/javascript/dashboard/components-next/{Label => label}/story/AddLabel.story.vue (100%)
rename app/javascript/dashboard/components-next/{Label => label}/story/Label.story.vue (100%)
rename app/javascript/dashboard/components-next/{Label => label}/story/fixtures.js (100%)
create mode 100644 app/javascript/dashboard/components-next/select/Select.vue
create mode 100644 app/javascript/dashboard/components-next/table/BaseTable.story.vue
create mode 100644 app/javascript/dashboard/components-next/table/BaseTable.vue
create mode 100644 app/javascript/dashboard/components-next/table/BaseTableCell.vue
create mode 100644 app/javascript/dashboard/components-next/table/BaseTableRow.vue
create mode 100644 app/javascript/dashboard/components-next/table/index.js
delete mode 100644 app/javascript/dashboard/components/FormSection.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/inbox/WidgetBuilder.vue
rename app/javascript/dashboard/routes/dashboard/settings/sla/{components => }/SLAPaywallEnterprise.vue (87%)
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/BaseEmptyState.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/SLABusinessHoursLabel.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/SLAEmptyState.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/SLAHeader.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/SLAListItem.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/SLAListItemLoading.vue
delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/sla/components/SLAResponseTime.vue
diff --git a/app/javascript/dashboard/assets/scss/_base.scss b/app/javascript/dashboard/assets/scss/_base.scss
index 14ae2a9d4..d3d3f6726 100644
--- a/app/javascript/dashboard/assets/scss/_base.scss
+++ b/app/javascript/dashboard/assets/scss/_base.scss
@@ -66,7 +66,7 @@ textarea {
// Field base styles (Input, TextArea, Select)
@layer components {
.field-base {
- @apply block box-border w-full transition-colors duration-[0.25s] ease-[ease-in-out] focus:outline-n-brand dark:focus:outline-n-brand appearance-none mx-0 mt-0 mb-4 py-2 px-3 rounded-lg text-base font-normal bg-n-alpha-black2 placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 text-n-slate-12 border-none outline outline-1 outline-n-weak dark:outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6;
+ @apply block box-border w-full transition-colors duration-[0.25s] ease-[ease-in-out] focus:outline-n-brand dark:focus:outline-n-brand appearance-none mx-0 mt-0 mb-4 py-2 px-3 rounded-lg text-sm font-normal bg-n-alpha-black2 placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 text-n-slate-12 border-none outline outline-1 outline-n-weak dark:outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6;
}
.field-disabled {
diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss
index 40ca7b436..81a4569b5 100644
--- a/app/javascript/dashboard/assets/scss/_woot.scss
+++ b/app/javascript/dashboard/assets/scss/_woot.scss
@@ -66,4 +66,84 @@ body {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
+
+ /**
+ * ============================================================================
+ * TYPOGRAPHY UTILITIES
+ * ============================================================================
+ *
+ * | Class | Use Case |
+ * |--------------------|----------------------------------------------------|
+ * | .text-body-main | , , general body text |
+ * | .text-body-para | for paragraphs, larger text blocks |
+ * | .text-heading-1 |
, page titles, panel headers |
+ * | .text-heading-2 | , section headings, card titles |
+ * | .text-heading-3 | , card headings, breadcrumbs, subsections |
+ * | .text-label | , form labels, field names |
+ * | .text-label-small | , footnotes, tags, badges, captions |
+ * | .text-button | , standard button text |
+ * | .text-button-small | , small/compact button text |
+ */
+
+ /* body-text-main: Main text style for general body text */
+ .text-body-main {
+ @apply font-inter text-sm font-420;
+ line-height: 21px; /* 150% */
+ letter-spacing: -0.28px;
+ }
+
+ /* body-text-paragraph: For paragraphs or larger blocks of text */
+ .text-body-para {
+ @apply font-inter text-sm font-420;
+ line-height: 21px; /* 150% */
+ letter-spacing: -0.21px;
+ }
+
+ /* heading-1: Large heading for pages and panels */
+ .text-heading-1 {
+ @apply font-inter text-lg font-520;
+ line-height: 24px; /* 133.333% */
+ letter-spacing: -0.27px;
+ }
+
+ /* heading-2: Secondary heading for sections */
+ .text-heading-2 {
+ @apply font-inter text-base font-medium;
+ line-height: 24px; /* 133.333% */
+ letter-spacing: -0.27px;
+ }
+
+ /* heading-3: For card headings, breadcrumbs, subsections */
+ .text-heading-3 {
+ @apply font-inter text-sm font-medium;
+ line-height: 21px; /* 150% */
+ letter-spacing: -0.27px;
+ }
+
+ /* label: Standard label text for form fields */
+ .text-label {
+ @apply font-inter text-sm font-medium;
+ line-height: 21px; /* 150% */
+ }
+
+ /* label-small: Smallest font for labels, footnotes, tags */
+ .text-label-small {
+ @apply font-inter text-xs font-440;
+ line-height: 16px; /* 133.333% */
+ letter-spacing: -0.24px;
+ }
+
+ /* button-text: Text for standard size buttons */
+ .text-button {
+ @apply font-inter text-sm font-460;
+ line-height: 21px; /* 150% */
+ letter-spacing: -0.28px;
+ }
+
+ /* button-text-small: Text for smaller buttons */
+ .text-button-small {
+ @apply font-inter text-xs font-440;
+ line-height: 18px; /* 150% */
+ letter-spacing: -0.24px;
+ }
}
diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/AgentCapacityPolicyCard/AgentCapacityPolicyCard.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/AgentCapacityPolicyCard/AgentCapacityPolicyCard.vue
index 3c749e751..aad1c2153 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/AgentCapacityPolicyCard/AgentCapacityPolicyCard.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/AgentCapacityPolicyCard/AgentCapacityPolicyCard.vue
@@ -49,7 +49,7 @@ const handleFetchUsers = () => {
diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentCard/AssignmentCard.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentCard/AssignmentCard.vue
index 1e477eafe..eb94d1ddd 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentCard/AssignmentCard.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentCard/AssignmentCard.vue
@@ -20,7 +20,7 @@ const handleClick = () => {
-
{{ title }}
+ {{ title }}
{
@click.stop="handleClick"
/>
-
{{ description }}
+
{{ description }}
{
-
+
{{ description }}
-
+
{{
`${t('ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.ORDER')}:`
}}
{{ order }}
-
+
{{
`${t('ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.PRIORITY')}:`
}}
diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/components/ExclusionRules.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/components/ExclusionRules.vue
index 965f532f4..3ac7dfd5f 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/components/ExclusionRules.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/components/ExclusionRules.vue
@@ -2,7 +2,7 @@
import { computed, ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import AddDataDropdown from 'dashboard/components-next/AssignmentPolicy/components/AddDataDropdown.vue';
-import LabelItem from 'dashboard/components-next/Label/LabelItem.vue';
+import LabelItem from 'dashboard/components-next/label/LabelItem.vue';
import DurationInput from 'dashboard/components-next/input/DurationInput.vue';
import { DURATION_UNITS } from 'dashboard/components-next/input/constants';
diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/components/RadioCard.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/components/RadioCard.vue
index ded6cb930..182325ed8 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/components/RadioCard.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/components/RadioCard.vue
@@ -1,5 +1,5 @@
-
+
-
+
{{ headerTitle }}
diff --git a/app/javascript/dashboard/components-next/Companies/CompaniesListLayout.vue b/app/javascript/dashboard/components-next/Companies/CompaniesListLayout.vue
index 7e9bcbacb..dce8c6af1 100644
--- a/app/javascript/dashboard/components-next/Companies/CompaniesListLayout.vue
+++ b/app/javascript/dashboard/components-next/Companies/CompaniesListLayout.vue
@@ -32,17 +32,18 @@ const updateCurrentPage = page => {
@search="emit('search', $event)"
@update:sort="emit('update:sort', $event)"
/>
-
-
+
+
-
+
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue b/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue
index bc1370a0a..48225d2cd 100644
--- a/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue
+++ b/app/javascript/dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue
@@ -3,8 +3,8 @@ import { computed, watch, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useMapGetter, useStore } from 'dashboard/composables/store';
-import LabelItem from 'dashboard/components-next/Label/LabelItem.vue';
-import AddLabel from 'dashboard/components-next/Label/AddLabel.vue';
+import LabelItem from 'dashboard/components-next/label/LabelItem.vue';
+import AddLabel from 'dashboard/components-next/label/AddLabel.vue';
const props = defineProps({
contactId: {
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactHeader.vue b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactHeader.vue
index b3590b51e..7f9047f8d 100644
--- a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactHeader.vue
+++ b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/ContactHeader.vue
@@ -32,9 +32,9 @@ const emit = defineEmits([
-
+
{{ headerTitle }}
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/components/ContactsActiveFiltersPreview.vue b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/components/ContactsActiveFiltersPreview.vue
index 88ee94604..4daf52e39 100644
--- a/app/javascript/dashboard/components-next/Contacts/ContactsHeader/components/ContactsActiveFiltersPreview.vue
+++ b/app/javascript/dashboard/components-next/Contacts/ContactsHeader/components/ContactsActiveFiltersPreview.vue
@@ -62,7 +62,7 @@ const activeFilterQueryData = computed(() => {
t('CONTACTS_LAYOUT.FILTER.ACTIVE_FILTERS.CLEAR_FILTERS')
"
:show-clear-button="!hasActiveSegments"
- class="max-w-[60rem] px-6"
+ class="max-w-5xl"
@open-filter="emit('openFilter')"
@clear-filters="emit('clearFilters')"
/>
diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsListLayout.vue b/app/javascript/dashboard/components-next/Contacts/ContactsListLayout.vue
index 0646387fb..b38f44018 100644
--- a/app/javascript/dashboard/components-next/Contacts/ContactsListLayout.vue
+++ b/app/javascript/dashboard/components-next/Contacts/ContactsListLayout.vue
@@ -98,8 +98,8 @@ const showPagination = computed(() => {
@apply-filter="emit('applyFilter', $event)"
@clear-filters="emit('clearFilters')"
/>
-
-
+
+
{
/>
-
+
diff --git a/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue b/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue
index f5822f67d..624ba2981 100644
--- a/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue
+++ b/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue
@@ -1,8 +1,10 @@
-
-
-
-
- {{ attribute.label }}
-
-
-
-
-
-
{{ attribute.type }}
+
+
+
+
+
+
+
+
+
+ {{ attribute.label }}
+
+
-
-
-
-
{{ attribute.value }}
+
+
+
+
+ {{ attribute.value }}
+
+
+
+
+ {{ attribute.attribute_description || attribute.description }}
+
+
+
-
-
- {{ attribute.attribute_description || attribute.description || '' }}
-
diff --git a/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributeItem.vue b/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributeItem.vue
index 70b4a7af5..4697a15d7 100644
--- a/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributeItem.vue
+++ b/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributeItem.vue
@@ -35,18 +35,20 @@ const handleDelete = () => {
-
+
{{ attribute.label }}
- {{ attribute.type }}
+ {{ attribute.type }}
- {{ attribute.value }}
+ {{
+ attribute.value
+ }}
diff --git a/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributes.vue b/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributes.vue
index e611dd637..019e3fb69 100644
--- a/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributes.vue
+++ b/app/javascript/dashboard/components-next/ConversationWorkflow/ConversationRequiredAttributes.vue
@@ -129,10 +129,10 @@ const handleDelete = attribute => {
-
+
{{ $t('CONVERSATION_WORKFLOW.REQUIRED_ATTRIBUTES.TITLE') }}
-
+
{{ $t('CONVERSATION_WORKFLOW.REQUIRED_ATTRIBUTES.DESCRIPTION') }}
diff --git a/app/javascript/dashboard/components-next/CustomAttributes/AttributeBadge.vue b/app/javascript/dashboard/components-next/CustomAttributes/AttributeBadge.vue
index fb0c9867a..a0494f44b 100644
--- a/app/javascript/dashboard/components-next/CustomAttributes/AttributeBadge.vue
+++ b/app/javascript/dashboard/components-next/CustomAttributes/AttributeBadge.vue
@@ -2,6 +2,7 @@
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import Icon from 'dashboard/components-next/icon/Icon.vue';
+import Label from 'dashboard/components-next/label/Label.vue';
const props = defineProps({
type: {
@@ -18,11 +19,13 @@ const attributeConfig = {
colorClass: 'text-n-blue-11',
icon: 'i-lucide-message-circle',
labelKey: 'ATTRIBUTES_MGMT.BADGES.PRE_CHAT',
+ color: 'slate',
},
resolution: {
colorClass: 'text-n-teal-11',
icon: 'i-lucide-circle-check-big',
labelKey: 'ATTRIBUTES_MGMT.BADGES.RESOLUTION',
+ color: 'slate',
},
};
const config = computed(
@@ -31,12 +34,9 @@ const config = computed(
-
-
- {{
- t(config.labelKey)
- }}
-
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/EmptyStateLayout.vue b/app/javascript/dashboard/components-next/EmptyStateLayout.vue
index 4c506dd14..f782886e8 100644
--- a/app/javascript/dashboard/components-next/EmptyStateLayout.vue
+++ b/app/javascript/dashboard/components-next/EmptyStateLayout.vue
@@ -26,7 +26,7 @@ defineProps({
class="relative flex flex-col items-center justify-center w-full h-full overflow-hidden"
>
{
-
-
+
+
{
-
-
+
+
-
+
diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/LocalePage/AddLocaleDialog.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/LocalePage/AddLocaleDialog.vue
index 696cc837a..30044a564 100644
--- a/app/javascript/dashboard/components-next/HelpCenter/Pages/LocalePage/AddLocaleDialog.vue
+++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/LocalePage/AddLocaleDialog.vue
@@ -62,6 +62,7 @@ const onCreate = async () => {
from: route.name,
});
+ selectedLocale.value = '';
dialogRef.value?.close();
useAlert(
t('HELP_CENTER.LOCALES_PAGE.ADD_LOCALE_DIALOG.API.SUCCESS_MESSAGE')
diff --git a/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js b/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js
index 3de1fad0d..105fe46a0 100644
--- a/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js
+++ b/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js
@@ -70,7 +70,7 @@ describe('composeConversationHelper', () => {
const result = helpers.buildContactableInboxesList(inboxes);
expect(result[0]).toMatchObject({
id: 1,
- icon: 'i-ri-mail-line',
+ icon: 'i-woot-mail',
label: 'Email Inbox (support@example.com)',
action: 'inbox',
value: 1,
diff --git a/app/javascript/dashboard/components-next/Settings/SettingsAccordion.vue b/app/javascript/dashboard/components-next/Settings/SettingsAccordion.vue
new file mode 100644
index 000000000..2bd2f8191
--- /dev/null
+++ b/app/javascript/dashboard/components-next/Settings/SettingsAccordion.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/Settings/SettingsFieldSection.vue b/app/javascript/dashboard/components-next/Settings/SettingsFieldSection.vue
new file mode 100644
index 000000000..2c15e55c5
--- /dev/null
+++ b/app/javascript/dashboard/components-next/Settings/SettingsFieldSection.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/Settings/SettingsToggleSection.vue b/app/javascript/dashboard/components-next/Settings/SettingsToggleSection.vue
new file mode 100644
index 000000000..62e8b44fa
--- /dev/null
+++ b/app/javascript/dashboard/components-next/Settings/SettingsToggleSection.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+ {{ header }}
+
+
+
+
+ {{ description }}
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/avatar/Avatar.vue b/app/javascript/dashboard/components-next/avatar/Avatar.vue
index 1618cef21..c98c8d43a 100644
--- a/app/javascript/dashboard/components-next/avatar/Avatar.vue
+++ b/app/javascript/dashboard/components-next/avatar/Avatar.vue
@@ -112,6 +112,19 @@ const containerStyles = computed(() => ({
height: `${props.size}px`,
}));
+const borderRadiusClass = computed(() => {
+ if (props.roundedFull) {
+ return 'rounded-full';
+ }
+
+ // Approximates 25% of size
+ if (props.size <= 16) return 'rounded'; // 4px
+ if (props.size <= 24) return 'rounded-md'; // 6px
+ if (props.size <= 32) return 'rounded-lg'; // 8px
+ if (props.size <= 48) return 'rounded-xl'; // 12px
+ return 'rounded-2xl'; // 16px
+});
+
const avatarStyles = computed(() => ({
...containerStyles.value,
backgroundColor:
@@ -184,7 +197,7 @@ watch(
@@ -216,9 +229,9 @@ watch(
{
-
+
@@ -140,7 +140,9 @@ const handleCreateAssistant = () => {
>
{
-
+
{
-
+
diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/Paywall.vue b/app/javascript/dashboard/components-next/captain/pageComponents/Paywall.vue
index b9deb0ff0..2b896a905 100644
--- a/app/javascript/dashboard/components-next/captain/pageComponents/Paywall.vue
+++ b/app/javascript/dashboard/components-next/captain/pageComponents/Paywall.vue
@@ -27,7 +27,7 @@ const openBilling = () => {
{
it('returns correct icon for Voice channel', () => {
const inbox = { channel_type: 'Channel::Voice' };
const { value: icon } = useChannelIcon(inbox);
- expect(icon).toBe('i-ri-phone-fill');
+ expect(icon).toBe('i-woot-voice');
});
it('returns correct icon for Line channel', () => {
@@ -46,7 +46,7 @@ describe('useChannelIcon', () => {
it('returns correct icon for Twitter channel', () => {
const inbox = { channel_type: 'Channel::TwitterProfile' };
const { value: icon } = useChannelIcon(inbox);
- expect(icon).toBe('i-ri-twitter-x-fill');
+ expect(icon).toBe('i-woot-x');
});
it('returns correct icon for WebWidget channel', () => {
diff --git a/app/javascript/dashboard/components-next/input/Input.vue b/app/javascript/dashboard/components-next/input/Input.vue
index 561f98ffe..e5b5d4aa3 100644
--- a/app/javascript/dashboard/components-next/input/Input.vue
+++ b/app/javascript/dashboard/components-next/input/Input.vue
@@ -108,7 +108,7 @@ onMounted(() => {
{{ label }}
@@ -145,7 +145,7 @@ onMounted(() => {
/>
{{ message }}
diff --git a/app/javascript/dashboard/components-next/Label/AddLabel.vue b/app/javascript/dashboard/components-next/label/AddLabel.vue
similarity index 100%
rename from app/javascript/dashboard/components-next/Label/AddLabel.vue
rename to app/javascript/dashboard/components-next/label/AddLabel.vue
diff --git a/app/javascript/dashboard/components-next/label/Label.vue b/app/javascript/dashboard/components-next/label/Label.vue
new file mode 100644
index 000000000..a945de713
--- /dev/null
+++ b/app/javascript/dashboard/components-next/label/Label.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+ {{ labelTitle }}
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/Label/LabelItem.vue b/app/javascript/dashboard/components-next/label/LabelItem.vue
similarity index 100%
rename from app/javascript/dashboard/components-next/Label/LabelItem.vue
rename to app/javascript/dashboard/components-next/label/LabelItem.vue
diff --git a/app/javascript/dashboard/components-next/Label/story/AddLabel.story.vue b/app/javascript/dashboard/components-next/label/story/AddLabel.story.vue
similarity index 100%
rename from app/javascript/dashboard/components-next/Label/story/AddLabel.story.vue
rename to app/javascript/dashboard/components-next/label/story/AddLabel.story.vue
diff --git a/app/javascript/dashboard/components-next/Label/story/Label.story.vue b/app/javascript/dashboard/components-next/label/story/Label.story.vue
similarity index 100%
rename from app/javascript/dashboard/components-next/Label/story/Label.story.vue
rename to app/javascript/dashboard/components-next/label/story/Label.story.vue
diff --git a/app/javascript/dashboard/components-next/Label/story/fixtures.js b/app/javascript/dashboard/components-next/label/story/fixtures.js
similarity index 100%
rename from app/javascript/dashboard/components-next/Label/story/fixtures.js
rename to app/javascript/dashboard/components-next/label/story/fixtures.js
diff --git a/app/javascript/dashboard/components-next/pagination/PaginationFooter.vue b/app/javascript/dashboard/components-next/pagination/PaginationFooter.vue
index 476ba4d60..6bd0413e0 100644
--- a/app/javascript/dashboard/components-next/pagination/PaginationFooter.vue
+++ b/app/javascript/dashboard/components-next/pagination/PaginationFooter.vue
@@ -71,10 +71,10 @@ const pageInfo = computed(() => {
-
+
{{ currentPageInformation }}
@@ -97,11 +97,13 @@ const pageInfo = computed(() => {
:disabled="isFirstPage"
@click="changePage(currentPage - 1)"
/>
-
-
+
+
{{ formatFullNumber(currentPage) }}
-
+
{{ pageInfo }}
diff --git a/app/javascript/dashboard/components-next/select/Select.vue b/app/javascript/dashboard/components-next/select/Select.vue
new file mode 100644
index 000000000..9fd7af1ed
--- /dev/null
+++ b/app/javascript/dashboard/components-next/select/Select.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+ {{ placeholder }}
+
+
+
+
+ {{ option.label }}
+
+
+
+
+
+ {{ option.label }}
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/sidebar/ChannelLeaf.vue b/app/javascript/dashboard/components-next/sidebar/ChannelLeaf.vue
index 3f41a5c41..83d1d4e20 100644
--- a/app/javascript/dashboard/components-next/sidebar/ChannelLeaf.vue
+++ b/app/javascript/dashboard/components-next/sidebar/ChannelLeaf.vue
@@ -25,8 +25,8 @@ const reauthorizationRequired = computed(() => {
-
-
+
+
{{ label }}
{
children: sortedInboxes.value.map(inbox => ({
name: `${inbox.name}-${inbox.id}`,
label: inbox.name,
- icon: h(ChannelIcon, { inbox, class: 'size-[12px]' }),
+ icon: h(ChannelIcon, { inbox, class: 'size-[16px]' }),
to: accountScopedRoute('inbox_dashboard', { inbox_id: inbox.id }),
component: leafProps =>
h(ChannelLeaf, {
@@ -595,6 +595,16 @@ const menuItems = computed(() => {
name: 'Settings Teams',
label: t('SIDEBAR.TEAMS'),
icon: 'i-lucide-users',
+ activeOn: [
+ 'settings_teams_list',
+ 'settings_teams_new',
+ 'settings_teams_finish',
+ 'settings_teams_add_agents',
+ 'settings_teams_show',
+ 'settings_teams_edit',
+ 'settings_teams_edit_members',
+ 'settings_teams_edit_finish',
+ ],
to: accountScopedRoute('settings_teams_list'),
},
...(hasAdvancedAssignment.value
@@ -603,6 +613,15 @@ const menuItems = computed(() => {
name: 'Settings Agent Assignment',
label: t('SIDEBAR.AGENT_ASSIGNMENT'),
icon: 'i-lucide-user-cog',
+ activeOn: [
+ 'assignment_policy_index',
+ 'agent_assignment_policy_index',
+ 'agent_assignment_policy_create',
+ 'agent_assignment_policy_edit',
+ 'agent_capacity_policy_index',
+ 'agent_capacity_policy_create',
+ 'agent_capacity_policy_edit',
+ ],
to: accountScopedRoute('assignment_policy_index'),
},
]
@@ -611,6 +630,14 @@ const menuItems = computed(() => {
name: 'Settings Inboxes',
label: t('SIDEBAR.INBOXES'),
icon: 'i-lucide-inbox',
+ activeOn: [
+ 'settings_inbox_list',
+ 'settings_inbox_show',
+ 'settings_inbox_new',
+ 'settings_inbox_finish',
+ 'settings_inboxes_page_channel',
+ 'settings_inboxes_add_agents',
+ ],
to: accountScopedRoute('settings_inbox_list'),
},
{
@@ -703,7 +730,7 @@ const menuItems = computed(() => {
closeMobileSidebar,
{ ignore: ['#mobile-sidebar-launcher'] },
]"
- class="bg-n-background flex flex-col text-sm pb-0.5 fixed top-0 ltr:left-0 rtl:right-0 h-full z-40 w-[200px] md:w-auto md:relative md:flex-shrink-0 md:ltr:translate-x-0 md:rtl:translate-x-0 ltr:border-r rtl:border-l border-n-weak"
+ class="bg-n-background flex flex-col text-sm pb-px fixed top-0 ltr:left-0 rtl:right-0 h-full z-40 w-[200px] md:w-auto md:relative md:flex-shrink-0 md:ltr:translate-x-0 md:rtl:translate-x-0 ltr:border-r rtl:border-l border-n-weak"
:class="[
{
'shadow-lg md:shadow-none': isMobileSidebarOpen,
@@ -824,7 +851,7 @@ const menuItems = computed(() => {
"
/>
{
:active
/>
-
- {{ label }}
+
+
+
+ {{ label }}
diff --git a/app/javascript/dashboard/components-next/switch/Switch.vue b/app/javascript/dashboard/components-next/switch/Switch.vue
index 7bac2cb9e..93dd8862c 100644
--- a/app/javascript/dashboard/components-next/switch/Switch.vue
+++ b/app/javascript/dashboard/components-next/switch/Switch.vue
@@ -19,20 +19,24 @@ const updateValue = () => {
{{ t('SWITCH.TOGGLE') }}
+ >
+
+
diff --git a/app/javascript/dashboard/components-next/table/BaseTable.story.vue b/app/javascript/dashboard/components-next/table/BaseTable.story.vue
new file mode 100644
index 000000000..d0115c367
--- /dev/null
+++ b/app/javascript/dashboard/components-next/table/BaseTable.story.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ automation.name }}
+
+
+
+ {{ automation.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ automation.createdOn }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ agent.name }}
+
+
+ {{ agent.email }}
+
+
+
+
+
+
+
+ {{ agent.role }}
+
+
+
+
+
+ {{ agent.verified ? 'Verified' : 'Pending' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/table/BaseTable.vue b/app/javascript/dashboard/components-next/table/BaseTable.vue
new file mode 100644
index 000000000..9585b1800
--- /dev/null
+++ b/app/javascript/dashboard/components-next/table/BaseTable.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+ {{ header }}
+
+
+
+
+
+
+
+
+
+
+ {{ noDataMessage }}
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/table/BaseTableCell.vue b/app/javascript/dashboard/components-next/table/BaseTableCell.vue
new file mode 100644
index 000000000..19281a5ee
--- /dev/null
+++ b/app/javascript/dashboard/components-next/table/BaseTableCell.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/table/BaseTableRow.vue b/app/javascript/dashboard/components-next/table/BaseTableRow.vue
new file mode 100644
index 000000000..a844a7bca
--- /dev/null
+++ b/app/javascript/dashboard/components-next/table/BaseTableRow.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/table/index.js b/app/javascript/dashboard/components-next/table/index.js
new file mode 100644
index 000000000..0c3f4fc55
--- /dev/null
+++ b/app/javascript/dashboard/components-next/table/index.js
@@ -0,0 +1,3 @@
+export { default as BaseTable } from './BaseTable.vue';
+export { default as BaseTableRow } from './BaseTableRow.vue';
+export { default as BaseTableCell } from './BaseTableCell.vue';
diff --git a/app/javascript/dashboard/components-next/year-in-review/ShareModal.vue b/app/javascript/dashboard/components-next/year-in-review/ShareModal.vue
index 12a556b0e..079b98574 100644
--- a/app/javascript/dashboard/components-next/year-in-review/ShareModal.vue
+++ b/app/javascript/dashboard/components-next/year-in-review/ShareModal.vue
@@ -18,7 +18,7 @@ const props = defineProps({
},
year: {
type: [Number, String],
- required: true,
+ default: '',
},
});
diff --git a/app/javascript/dashboard/components/FormSection.vue b/app/javascript/dashboard/components/FormSection.vue
deleted file mode 100644
index d622d470d..000000000
--- a/app/javascript/dashboard/components/FormSection.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
- {{ title }}
-
-
-
- {{ description }}
-
-
-
-
-
-
-
diff --git a/app/javascript/dashboard/components/widgets/InboxName.vue b/app/javascript/dashboard/components/widgets/InboxName.vue
index ecdaa8a6f..583c912a3 100644
--- a/app/javascript/dashboard/components/widgets/InboxName.vue
+++ b/app/javascript/dashboard/components/widgets/InboxName.vue
@@ -10,12 +10,9 @@ defineProps({
-
-
-
+
+
+
{{ inbox.name }}
diff --git a/app/javascript/dashboard/components/widgets/LoadingState.vue b/app/javascript/dashboard/components/widgets/LoadingState.vue
index b68cd2b6c..c69d1e283 100644
--- a/app/javascript/dashboard/components/widgets/LoadingState.vue
+++ b/app/javascript/dashboard/components/widgets/LoadingState.vue
@@ -11,7 +11,7 @@ defineProps({
-
+
{{ message }}
diff --git a/app/javascript/dashboard/components/widgets/SettingIntroBanner.vue b/app/javascript/dashboard/components/widgets/SettingIntroBanner.vue
index 4f7668604..969303454 100644
--- a/app/javascript/dashboard/components/widgets/SettingIntroBanner.vue
+++ b/app/javascript/dashboard/components/widgets/SettingIntroBanner.vue
@@ -15,7 +15,7 @@ export default {
-
+
{{ headerTitle }}
diff --git a/app/javascript/dashboard/components/widgets/forms/Input.vue b/app/javascript/dashboard/components/widgets/forms/Input.vue
index b6ee69ec8..2b40d10df 100644
--- a/app/javascript/dashboard/components/widgets/forms/Input.vue
+++ b/app/javascript/dashboard/components/widgets/forms/Input.vue
@@ -61,7 +61,7 @@ export default {
- {{ label }}
+ {{ label }}
- {{ helpText }}
+ {{ helpText }}
{{ error }}
@@ -81,7 +81,7 @@ export default {
diff --git a/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue b/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue
index 3e84da086..9e8066140 100644
--- a/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue
+++ b/app/javascript/dashboard/routes/dashboard/campaigns/pages/CampaignsPageRouteView.vue
@@ -16,7 +16,7 @@ onMounted(() => {
diff --git a/app/javascript/dashboard/routes/dashboard/companies/pages/CompaniesIndex.vue b/app/javascript/dashboard/routes/dashboard/companies/pages/CompaniesIndex.vue
index 236de594a..28071421c 100644
--- a/app/javascript/dashboard/routes/dashboard/companies/pages/CompaniesIndex.vue
+++ b/app/javascript/dashboard/routes/dashboard/companies/pages/CompaniesIndex.vue
@@ -154,7 +154,7 @@ onMounted(() => {
t('COMPANIES.EMPTY_STATE.TITLE')
}}
-
+
{
{{ emptyStateMessage }}
-
+
-
+
-
+
{{ $t('INBOX.LIST.TITLE') }}
@@ -91,7 +93,7 @@ export default {
trailing-icon
slate
xs
- faded
+ :variant="showInboxDisplayMenu ? 'faded' : 'solid'"
@click="openInboxDisplayMenu"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue b/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue
index 9e34cb384..b70acccad 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/SettingsLayout.vue
@@ -20,7 +20,7 @@ defineProps({
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/SettingsSubPageHeader.vue b/app/javascript/dashboard/routes/dashboard/settings/SettingsSubPageHeader.vue
index 329f1f750..221efc3bf 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/SettingsSubPageHeader.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/SettingsSubPageHeader.vue
@@ -8,13 +8,13 @@ export default {
-
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/SettingsWrapper.vue b/app/javascript/dashboard/routes/dashboard/settings/SettingsWrapper.vue
index 28361e065..72cc147bc 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/SettingsWrapper.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/SettingsWrapper.vue
@@ -1,22 +1,26 @@
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/Wrapper.vue b/app/javascript/dashboard/routes/dashboard/settings/Wrapper.vue
index 09941f046..ba62100ae 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/Wrapper.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/Wrapper.vue
@@ -8,7 +8,6 @@ const props = defineProps({
keepAlive: { type: Boolean, default: true },
showBackButton: { type: Boolean, default: false },
backUrl: { type: [String, Object], default: '' },
- fullWidth: { type: Boolean, default: false },
});
const { t } = useI18n();
@@ -19,27 +18,21 @@ const showSettingsHeader = computed(
-
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue
index f796635c4..2ec298f98 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/account/Index.vue
@@ -146,12 +146,13 @@ export default {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue
index 850c55676..f081cec2f 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue
@@ -93,7 +93,7 @@ const handleClick = key => {
-
+
{
-
+
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentEditPage.vue b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentEditPage.vue
index faf790753..dfae60350 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentEditPage.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentEditPage.vue
@@ -251,9 +251,12 @@ watch(routeId, fetchPolicyData, { immediate: true });
-
+
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentIndexPage.vue b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentIndexPage.vue
index be5297a16..69c6a7e1a 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentIndexPage.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentAssignmentIndexPage.vue
@@ -96,7 +96,7 @@ onMounted(() => {
"
>
-
+
{{
@@ -108,7 +108,7 @@ onMounted(() => {
-
+
{
-
+
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityEditPage.vue b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityEditPage.vue
index 43f928f14..390608733 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityEditPage.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityEditPage.vue
@@ -184,9 +184,12 @@ onMounted(() => store.dispatch('agents/get'));
-
+
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityIndexPage.vue b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityIndexPage.vue
index fb94e5fb4..670ea767e 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityIndexPage.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/pages/AgentCapacityIndexPage.vue
@@ -94,7 +94,7 @@ onMounted(() => {
"
>
-
+
{{
@@ -106,7 +106,7 @@ onMounted(() => {
-
+
({
key,
label: t(`${BASE_KEY}.FORM.${type}.${key.toUpperCase()}.LABEL`),
@@ -102,6 +103,7 @@ const createOption = (
isActive: state[stateKey] === key,
disabled,
disabledMessage,
+ disabledLabel,
});
const assignmentOrderOptions = computed(() => {
@@ -116,13 +118,17 @@ const assignmentOrderOptions = computed(() => {
const disabledMessage = disabled
? t(`${BASE_KEY}.FORM.ASSIGNMENT_ORDER.BALANCED.PREMIUM_MESSAGE`)
: '';
+ const disabledLabel = disabled
+ ? t(`${BASE_KEY}.FORM.ASSIGNMENT_ORDER.BALANCED.PREMIUM_BADGE`)
+ : '';
return createOption(
'ASSIGNMENT_ORDER',
key,
'assignmentOrder',
disabled,
- disabledMessage
+ disabledMessage,
+ disabledLabel
);
});
});
@@ -217,6 +223,7 @@ defineExpose({
:description="option.description"
:is-active="option.isActive"
:disabled="option.disabled"
+ :disabled-label="option.disabledLabel"
:disabled-message="option.disabledMessage"
@select="state[section.key] = $event"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue
index 13b980c75..2aa2f28cd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/attributes/Index.vue
@@ -2,6 +2,7 @@
import { computed, onMounted, ref } from 'vue';
import { useToggle } from '@vueuse/core';
import { useAlert } from 'dashboard/composables';
+import { picoSearch } from '@scmmishra/pico-search';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import AddAttribute from './AddAttribute.vue';
import EditAttribute from './EditAttribute.vue';
@@ -26,6 +27,7 @@ const inboxes = useMapGetter('inboxes/getInboxes');
const [showAddPopup, toggleAddPopup] = useToggle(false);
const selectedTabIndex = ref(0);
+const searchQuery = ref('');
const uiFlags = computed(() => getters['attributes/getUIFlags'].value);
const [showEditPopup, toggleEditPopup] = useToggle(false);
const [showDeletePopup, toggleDeletePopup] = useToggle(false);
@@ -77,6 +79,7 @@ const attributes = computed(() =>
const onClickTabChange = tab => {
selectedTabIndex.value = tab.key;
+ searchQuery.value = '';
};
const handleEditAttribute = attribute => {
@@ -144,6 +147,16 @@ const derivedAttributes = computed(() =>
badges: buildBadges(attribute),
}))
);
+
+const filteredAttributes = computed(() => {
+ const query = searchQuery.value.trim();
+ if (!query) return derivedAttributes.value;
+ return picoSearch(derivedAttributes.value, query, [
+ 'attribute_display_name',
+ 'attribute_key',
+ 'attribute_description',
+ ]);
+});
@@ -153,31 +166,48 @@ const derivedAttributes = computed(() =>
>
+
+
+ {{ $t('ATTRIBUTES_MGMT.COUNT', { n: attributes.length }) }}
+
+
+
+
+
-
-
-
+
+
+ {{ $t('ATTRIBUTES_MGMT.NO_RESULTS') }}
+
+
{
-
-
-
-
-
+
+
-
- {{ $t('AUDIT_LOGS.LIST.404') }}
-
-
-
-
-
+
+
+
+
+
- {{ thHeader }}
-
-
-
-
-
- {{ generateLogText(auditLogItem) }}
-
-
- {{
- messageTimestamp(
- auditLogItem.created_at,
- 'MMM dd, yyyy hh:mm a'
- )
- }}
-
-
- {{ auditLogItem.remote_address }}
-
-
-
-
-
+
+
+ {{ generateLogText(auditLogItem) }}
+
+
+
+
+
+ {{
+ messageTimestamp(
+ auditLogItem.created_at,
+ 'MMM dd, yyyy hh:mm a'
+ )
+ }}
+
+
+
+
+
+ {{ auditLogItem.remote_address }}
+
+
+
+
+
+
+
-
-
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue
index 540b9dfa8..3117a51e2 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue
@@ -3,6 +3,7 @@ import { computed } from 'vue';
import { messageStamp } from 'shared/helpers/timeHelper';
import Button from 'dashboard/components-next/button/Button.vue';
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
+import { BaseTableRow, BaseTableCell } from 'dashboard/components-next/table';
const props = defineProps({
automation: {
@@ -35,47 +36,58 @@ const automationActive = computed({
-
- {{ automation.name }}
- {{ automation.description }}
-
-
-
-
- {{ readableDate(automation.created_on) }}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {{ automation.name }}
+
+
+
+ {{ automation.description }}
+
+
+
+
+
+
+
+
+
+
+ {{ readableDate(automation.created_on) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue
index 835a1d482..32139ccd5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue
@@ -7,8 +7,10 @@ import SettingsLayout from '../SettingsLayout.vue';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStoreGetters, useStore } from 'dashboard/composables/store';
+import { picoSearch } from '@scmmishra/pico-search';
import AutomationRuleRow from './AutomationRuleRow.vue';
import Button from 'dashboard/components-next/button/Button.vue';
+import { BaseTable } from 'dashboard/components-next/table';
const getters = useStoreGetters();
const store = useStore();
@@ -20,12 +22,19 @@ const addDialogRef = ref(null);
const editDialogRef = ref(null);
const showDeleteConfirmationPopup = ref(false);
const selectedAutomation = ref({});
+const searchQuery = ref('');
const toggleModalTitle = ref(t('AUTOMATION.TOGGLE.ACTIVATION_TITLE'));
const toggleModalDescription = ref(
t('AUTOMATION.TOGGLE.ACTIVATION_DESCRIPTION')
);
const records = computed(() => getters['automations/getAutomations'].value);
+
+const filteredRecords = computed(() => {
+ const query = searchQuery.value.trim();
+ if (!query) return records.value;
+ return picoSearch(records.value, query, ['name', 'description']);
+});
const uiFlags = computed(() => getters['automations/getUIFlags'].value);
const accountId = computed(() => getters.getCurrentAccountId.value);
@@ -165,9 +174,9 @@ const toggleAutomation = async ({ id, name, status }) => {
const tableHeaders = computed(() => {
return [
t('AUTOMATION.LIST.TABLE_HEADER.NAME'),
- t('AUTOMATION.LIST.TABLE_HEADER.DESCRIPTION'),
t('AUTOMATION.LIST.TABLE_HEADER.ACTIVE'),
t('AUTOMATION.LIST.TABLE_HEADER.CREATED_ON'),
+ t('AUTOMATION.LIST.TABLE_HEADER.ACTIONS'),
];
});
@@ -181,34 +190,38 @@ const tableHeaders = computed(() => {
>
+
+
+ {{ $t('AUTOMATION.COUNT', { n: records.length }) }}
+
+
-
-
-
- {{ thHeader }}
-
-
-
+
+
{
@edit="openEditPopup"
@delete="openDeletePopup"
/>
-
-
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue
index 0071eab03..9592deb85 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue
@@ -2,13 +2,21 @@
import { useAlert } from 'dashboard/composables';
import AddCanned from './AddCanned.vue';
import EditCanned from './EditCanned.vue';
+import SettingsLayout from '../SettingsLayout.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
import { computed, onMounted, ref, defineOptions } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStoreGetters, useStore } from 'dashboard/composables/store';
+import { picoSearch } from '@scmmishra/pico-search';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import Button from 'dashboard/components-next/button/Button.vue';
+import Icon from 'dashboard/components-next/icon/Icon.vue';
+import {
+ BaseTable,
+ BaseTableRow,
+ BaseTableCell,
+} from 'dashboard/components-next/table';
defineOptions({
name: 'CannedResponseSettings',
@@ -28,9 +36,17 @@ const activeResponse = ref({});
const cannedResponseAPI = ref({ message: '' });
const sortOrder = ref('asc');
+const searchQuery = ref('');
+
const records = computed(() =>
getters.getSortedCannedResponses.value(sortOrder.value)
);
+
+const filteredRecords = computed(() => {
+ const query = searchQuery.value.trim();
+ if (!query) return records.value;
+ return picoSearch(records.value, query, ['short_code', 'content']);
+});
const uiFlags = computed(() => getters.getUIFlags.value);
const deleteConfirmText = computed(
@@ -114,103 +130,118 @@ const confirmDeletion = () => {
const tableHeaders = computed(() => {
return [
t('CANNED_MGMT.LIST.TABLE_HEADER.SHORT_CODE'),
- t('CANNED_MGMT.LIST.TABLE_HEADER.CONTENT'),
t('CANNED_MGMT.LIST.TABLE_HEADER.ACTIONS'),
];
});
-
-
-
-
-
-
-
-
-
-
+
+
- {{ $t('CANNED_MGMT.LIST.404') }}
-
-
-
-
-
- {{ thHeader }}
-
-
-
- {{ thHeader }}
-
-
-
-
-
-
-
-
- {{ cannedItem.short_code }}
-
-
- {{ getPlainText(cannedItem.content) }}
-
-
-
-
-
-
-
-
-
+
+
+ {{ $t('CANNED_MGMT.COUNT', { n: records.length }) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ tableHeaders[0] }}
+
+
+
+
+
+ {{ tableHeaders[1] }}
+
+
+
+
+
+
+
+
+ {{ cannedItem.short_code }}
+
+
+ {{ getPlainText(cannedItem.content) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -235,5 +266,5 @@ const tableHeaders = computed(() => {
:confirm-text="deleteConfirmText"
:reject-text="deleteRejectText"
/>
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue b/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue
index 311e4d9c0..167d13cf5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/components/BaseSettingsHeader.vue
@@ -1,9 +1,10 @@
-
+
-
-
-
-
-
-
+
+
+ {{ title }}
+
-
+
{{ description }}
@@ -85,7 +72,7 @@ const openInNewTab = url => {
:href="helpURL"
target="_blank"
rel="noopener noreferrer"
- class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-blue-11 hover:underline"
+ class="items-center hidden gap-1 text-sm font-medium sm:inline-flex w-fit text-n-blue-11 hover:underline mb-2"
>
{{ linkText }}
{
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/index.vue b/app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/index.vue
index 30df5bbd6..f862d2780 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/conversationWorkflow/index.vue
@@ -39,7 +39,7 @@ const showRequiredAttributes = computed(() => {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue
index 501313010..3b8af78fb 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/Index.vue
@@ -9,6 +9,8 @@ import Button from 'dashboard/components-next/button/Button.vue';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore, useMapGetter } from 'dashboard/composables/store';
+import { picoSearch } from '@scmmishra/pico-search';
+import { BaseTable } from 'dashboard/components-next/table';
const store = useStore();
const { t } = useI18n();
@@ -19,8 +21,15 @@ const selectedRole = ref(null);
const loading = ref({});
const showDeleteConfirmationPopup = ref(false);
const activeResponse = ref({});
+const searchQuery = ref('');
const records = useMapGetter('customRole/getCustomRoles');
+
+const filteredRecords = computed(() => {
+ const query = searchQuery.value.trim();
+ if (!query) return records.value;
+ return picoSearch(records.value, query, ['name', 'description']);
+});
const uiFlags = useMapGetter('customRole/getUIFlags');
const deleteConfirmText = computed(
@@ -129,15 +138,22 @@ const confirmDeletion = () => {
>
+
+
+ {{ $t('CUSTOM_ROLE.COUNT', { n: records.length }) }}
+
+
@@ -147,26 +163,25 @@ const confirmDeletion = () => {
-
-
-
-
- {{ thHeader }}
-
-
-
-
-
-
+
+
+
+
+
{
{{ thHeader }}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
index 3a080a0c1..1104848d5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
@@ -3,6 +3,7 @@ import { useI18n } from 'vue-i18n';
import { getI18nKey } from 'dashboard/routes/dashboard/settings/helper/settingsHelper';
import Button from 'dashboard/components-next/button/Button.vue';
+import { BaseTableRow, BaseTableCell } from 'dashboard/components-next/table';
defineProps({
roles: {
@@ -27,43 +28,49 @@ const getFormattedPermissions = role => {
-
-
-
- {{ customRole.name }}
-
-
- {{ customRole.description }}
-
-
- {{ getFormattedPermissions(customRole) }}
-
-
-
-
-
-
-
+
+
+
+
+ {{ customRole.name }}
+
+
+
+
+
+ {{ customRole.description }}
+
+
+
+
+
+ {{ getFormattedPermissions(customRole) }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue
index d6fe854a7..0c1494d09 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue
@@ -92,7 +92,7 @@ const channelList = computed(() => {
key: 'voice',
title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.VOICE.TITLE'),
description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.VOICE.DESCRIPTION'),
- icon: 'i-ri-phone-fill',
+ icon: 'i-woot-voice',
});
return channels;
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue
index 1eae2dd6f..a18a9a0f8 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue
@@ -1,14 +1,14 @@
-
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/InboxChannels.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/InboxChannels.vue
index f69a50064..d5a614c47 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/InboxChannels.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/InboxChannels.vue
@@ -59,7 +59,7 @@ const items = computed(() => {
-
+
{
return inboxes.value?.slice().sort((a, b) => a.name.localeCompare(b.name));
});
+const filteredInboxesList = computed(() => {
+ const query = searchQuery.value.trim();
+ if (!query) return inboxesList.value;
+ return picoSearch(inboxesList.value, query, ['name', 'channel_type']);
+});
+
const uiFlags = computed(() => getters['inboxes/getUIFlags'].value);
const deleteConfirmText = computed(
@@ -80,87 +88,93 @@ const openDelete = inbox => {
>
+
+
+ {{ $t('INBOX_MGMT.COUNT', { n: inboxesList.length }) }}
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
- {{ inbox.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {{ $t('INBOX_MGMT.NO_RESULTS') }}
+
+
+
+
+
+
+
+
+
+
+ {{ inbox.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -43,81 +43,64 @@ export default {
@end="onDragEnd"
>
-
-
-
+
+
+
+
+
{{ item.name }}
{{ item.type }}
-
+
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue
index bb7274db0..7f6ac7fe1 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue
@@ -1,159 +1,135 @@
-
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.DESCRIPTION') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS') }}
+
+
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.ENABLE.LABEL') }}
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.ENABLE.OPTIONS.ENABLED') }}
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.ENABLE.OPTIONS.DISABLED') }}
-
-
-
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.PRE_CHAT_MESSAGE.LABEL') }}
-
-
-
-
-
{{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS') }}
-
-
-
-
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS_HEADER.KEY') }}
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS_HEADER.TYPE') }}
-
-
- {{
- $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS_HEADER.REQUIRED')
- }}
-
-
- {{ $t('INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS_HEADER.LABEL') }}
-
-
- {{
- $t(
- 'INBOX_MGMT.PRE_CHAT_FORM.SET_FIELDS_HEADER.PLACE_HOLDER'
- )
- }}
+
+
+
+
+
+ {{ header }}
@@ -165,8 +141,8 @@ export default {
-
-
+ {
+ this.setTabFromRouteParam();
+ });
+ }
},
- immediate: false,
+ immediate: true,
},
},
mounted() {
- this.fetchInboxSettings();
- this.fetchPortals();
- this.fetchHealthData();
+ this.fetchSharedData();
},
methods: {
- fetchPortals() {
+ fetchSharedData() {
+ this.$store.dispatch('agents/get');
+ this.$store.dispatch('teams/get');
+ this.$store.dispatch('labels/get');
this.$store.dispatch('portals/index');
},
+ syncInboxData() {
+ if (!this.inbox || !this.inbox.id) return;
+
+ this.avatarUrl = this.inbox.avatar_url;
+ this.selectedInboxName = this.inbox.name;
+ this.webhookUrl = this.inbox.webhook_url;
+ this.greetingEnabled = this.inbox.greeting_enabled || false;
+ this.greetingMessage = this.inbox.greeting_message || '';
+ this.emailCollectEnabled = this.inbox.enable_email_collect;
+ this.senderNameType = this.inbox.sender_name_type;
+ this.businessName = this.inbox.business_name;
+ this.allowMessagesAfterResolved =
+ this.inbox.allow_messages_after_resolved;
+ this.continuityViaEmail = this.inbox.continuity_via_email;
+ this.channelWebsiteUrl = this.inbox.website_url;
+ this.channelWelcomeTitle = this.inbox.welcome_title;
+ this.channelWelcomeTagline = this.inbox.welcome_tagline || '';
+ this.selectedFeatureFlags = this.inbox.selected_feature_flags || [];
+ this.replyTime = this.inbox.reply_time;
+ this.locktoSingleConversation = this.inbox.lock_to_single_conversation;
+ this.selectedPortalSlug = this.inbox.help_center
+ ? this.inbox.help_center.slug
+ : '';
+
+ const savedBubbleSettings = LocalStorage.get(
+ this.widgetBuilderStorageKey
+ );
+ if (savedBubbleSettings) {
+ this.widgetBubblePosition = savedBubbleSettings.position || 'right';
+ this.widgetBubbleType = savedBubbleSettings.type || 'standard';
+ this.widgetBubbleLauncherTitle =
+ savedBubbleSettings.launcherTitle || '';
+ } else {
+ this.widgetBubblePosition = 'right';
+ this.widgetBubbleType = 'standard';
+ this.widgetBubbleLauncherTitle = '';
+ }
+ },
async fetchHealthData() {
if (!this.inbox) return;
@@ -357,17 +420,8 @@ export default {
}
return [...selected, current];
},
- refreshAvatarUrlOnTabChange(index) {
- // Refresh avatar URL on tab change from inbox-settings and widget-builder tabs, to ensure real-time updates
- if (
- this.inbox &&
- ['inbox-settings', 'widget-builder'].includes(this.tabs[index].key)
- )
- this.avatarUrl = this.inbox.avatar_url;
- },
onTabChange(selectedTabIndex) {
this.selectedTabIndex = selectedTabIndex;
- this.refreshAvatarUrlOnTabChange(selectedTabIndex);
this.updateRouteWithoutRefresh(selectedTabIndex);
},
updateRouteWithoutRefresh(selectedTabIndex) {
@@ -385,43 +439,21 @@ export default {
},
setTabFromRouteParam() {
const { tab: tabParam } = this.$route.params;
- if (!tabParam) return;
+ if (!tabParam) {
+ this.selectedTabIndex = 0;
+ return;
+ }
const tabIndex = this.tabs.findIndex(tab => tab.key === tabParam);
-
this.selectedTabIndex = tabIndex === -1 ? 0 : tabIndex;
},
- fetchInboxSettings() {
- this.selectedAgents = [];
- this.$store.dispatch('agents/get');
- this.$store.dispatch('teams/get');
- this.$store.dispatch('labels/get');
- this.$store.dispatch('inboxes/get').then(() => {
- this.avatarUrl = this.inbox.avatar_url;
- this.selectedInboxName = this.inbox.name;
- this.webhookUrl = this.inbox.webhook_url;
- this.greetingEnabled = this.inbox.greeting_enabled || false;
- this.greetingMessage = this.inbox.greeting_message || '';
- this.emailCollectEnabled = this.inbox.enable_email_collect;
- this.senderNameType = this.inbox.sender_name_type;
- this.businessName = this.inbox.business_name;
- this.allowMessagesAfterResolved =
- this.inbox.allow_messages_after_resolved;
- this.continuityViaEmail = this.inbox.continuity_via_email;
- this.channelWebsiteUrl = this.inbox.website_url;
- this.channelWelcomeTitle = this.inbox.welcome_title;
- this.channelWelcomeTagline = this.inbox.welcome_tagline || '';
- this.selectedFeatureFlags = this.inbox.selected_feature_flags || [];
- this.replyTime = this.inbox.reply_time;
- this.locktoSingleConversation = this.inbox.lock_to_single_conversation;
- this.selectedPortalSlug = this.inbox.help_center
- ? this.inbox.help_center.slug
- : '';
-
- // Set initial tab after inbox data is loaded
- this.setTabFromRouteParam();
- });
- },
async updateInbox() {
+ const bubbleSettings = {
+ position: this.widgetBubblePosition,
+ type: this.widgetBubbleType,
+ launcherTitle: this.widgetBubbleLauncherTitle,
+ };
+ LocalStorage.set(this.widgetBuilderStorageKey, bubbleSettings);
+
try {
const payload = {
id: this.currentInboxId,
@@ -433,7 +465,7 @@ export default {
portal_id: this.selectedPortalSlug
? this.portals.find(
portal => portal.slug === this.selectedPortalSlug
- ).id
+ )?.id || null
: null,
lock_to_single_conversation: this.locktoSingleConversation,
sender_name_type: this.senderNameType,
@@ -454,6 +486,7 @@ export default {
}
await this.$store.dispatch('inboxes/updateInbox', payload);
useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
+ this.showBusinessNameInput = false;
} catch (error) {
useAlert(error.message || this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
}
@@ -483,12 +516,13 @@ export default {
this.senderNameType = key;
},
onClickShowBusinessNameInput() {
- this.showBusinessNameInput = !this.showBusinessNameInput;
- if (this.showBusinessNameInput) {
- this.$nextTick(() => {
- this.$refs.businessNameInput.focus();
- });
- }
+ this.showBusinessNameInput = true;
+ this.$nextTick(() => {
+ this.$refs.businessNameInput?.focus();
+ });
+ },
+ hideBusinessNameInput() {
+ this.showBusinessNameInput = false;
},
},
validations: {
@@ -502,7 +536,14 @@ export default {
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
- {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.WIDGET_COLOR.LABEL') }}
-
-
-
-
- {{ $t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.LABEL') }}
-
-
-
-
- {{
- $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_GREETING_TOGGLE.LABEL')
- }}
-
-
- {{
- $t(
- 'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_GREETING_TOGGLE.ENABLED'
- )
- }}
-
-
- {{
- $t(
- 'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_GREETING_TOGGLE.DISABLED'
- )
- }}
-
-
-
- {{
- $t(
- 'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_GREETING_TOGGLE.HELP_TEXT'
- )
- }}
-
-
-
-
+
+
+ {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL') }}
+
+
+
+
+
+
+
-
-
- {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.REPLY_TIME.TITLE') }}
-
-
- {{
+ >
+
- {{
- $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.REPLY_TIME.IN_A_FEW_HOURS')
- }}
-
-
- {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.REPLY_TIME.IN_A_DAY') }}
-
-
+ "
+ :error="
+ v$.webhookUrl.$error
+ ? $t(
+ 'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.ERROR'
+ )
+ : ''
+ "
+ @blur="v$.webhookUrl.$touch"
+ />
+
-
- {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.REPLY_TIME.HELP_TEXT') }}
-
-
+
+
+
-
- {{ $t('INBOX_MGMT.SETTINGS_POPUP.ENABLE_EMAIL_COLLECT_BOX') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.EMAIL_COLLECT_BOX.ENABLED') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.EMAIL_COLLECT_BOX.DISABLED') }}
-
-
-
- {{
- $t(
- 'INBOX_MGMT.SETTINGS_POPUP.ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT'
- )
- }}
-
-
+
+
+
-
- {{ $t('INBOX_MGMT.SETTINGS_POPUP.ALLOW_MESSAGES_AFTER_RESOLVED') }}
-
-
- {{
- $t('INBOX_MGMT.EDIT.ALLOW_MESSAGES_AFTER_RESOLVED.ENABLED')
- }}
-
-
- {{
- $t('INBOX_MGMT.EDIT.ALLOW_MESSAGES_AFTER_RESOLVED.DISABLED')
- }}
-
-
-
- {{
- $t(
- 'INBOX_MGMT.SETTINGS_POPUP.ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT'
- )
- }}
-
-
+
+
+
-
- {{ $t('INBOX_MGMT.SETTINGS_POPUP.ENABLE_CONTINUITY_VIA_EMAIL') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.ENABLE_CONTINUITY_VIA_EMAIL.ENABLED') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.ENABLE_CONTINUITY_VIA_EMAIL.DISABLED') }}
-
-
-
- {{
- $t(
- 'INBOX_MGMT.SETTINGS_POPUP.ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT'
- )
- }}
-
-
-
-
- {{ $t('INBOX_MGMT.HELP_CENTER.LABEL') }}
-
-
-
- {{ $t('INBOX_MGMT.HELP_CENTER.PLACEHOLDER') }}
-
-
- {{ p.name }}
-
-
-
- {{ $t('INBOX_MGMT.HELP_CENTER.SUB_TEXT') }}
-
-
-
- {{ $t('INBOX_MGMT.SETTINGS_POPUP.LOCK_TO_SINGLE_CONVERSATION') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.LOCK_TO_SINGLE_CONVERSATION.ENABLED') }}
-
-
- {{ $t('INBOX_MGMT.EDIT.LOCK_TO_SINGLE_CONVERSATION.DISABLED') }}
-
-
-
- {{
- $t(
- 'INBOX_MGMT.SETTINGS_POPUP.LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT'
- )
- }}
-
-
-
-
- {{ $t('INBOX_MGMT.FEATURES.LABEL') }}
-
-
-
-
- {{ $t('INBOX_MGMT.FEATURES.DISPLAY_FILE_PICKER') }}
-
-
-
-
-
- {{ $t('INBOX_MGMT.FEATURES.DISPLAY_EMOJI_PICKER') }}
-
-
-
-
-
- {{ $t('INBOX_MGMT.FEATURES.ALLOW_END_CONVERSATION') }}
-
-
-
-
-
- {{ $t('INBOX_MGMT.FEATURES.USE_INBOX_AVATAR_FOR_BOT') }}
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ $t(
+ 'INBOX_MGMT.WIDGET_BUILDER.WIDGET_OPTIONS.WIDGET_BUBBLE_POSITION_LABEL'
+ )
+ }}
+
+
+
+
+
+
+ {{
+ $t(
+ 'INBOX_MGMT.WIDGET_BUILDER.WIDGET_OPTIONS.WIDGET_BUBBLE_TYPE_LABEL'
+ )
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue
index ddf8ee5fc..70805de69 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue
@@ -1,7 +1,7 @@
-
+ {{ $t('INBOX_MGMT.SMTP.TOGGLE_AVAILABILITY') }}
+
+ {{ $t('INBOX_MGMT.SMTP.TOGGLE_HELP') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/WidgetBuilder.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/WidgetBuilder.vue
deleted file mode 100644
index f2d1d8fa2..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/WidgetBuilder.vue
+++ /dev/null
@@ -1,443 +0,0 @@
-
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/google/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/google/Reauthorize.vue
index de9047ca7..769753e15 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/google/Reauthorize.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/google/Reauthorize.vue
@@ -44,8 +44,5 @@ async function requestAuthorization() {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/Reauthorize.vue
index 003017d1d..05dfc45b3 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/Reauthorize.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/Reauthorize.vue
@@ -30,8 +30,5 @@ async function requestAuthorization() {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/microsoft/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/microsoft/Reauthorize.vue
index e36ccd2a2..f7f8542a5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/microsoft/Reauthorize.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/microsoft/Reauthorize.vue
@@ -37,8 +37,5 @@ async function requestAuthorization() {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/tiktok/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/tiktok/Reauthorize.vue
index 5d48c6f8c..4d937ee47 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/tiktok/Reauthorize.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/tiktok/Reauthorize.vue
@@ -30,8 +30,5 @@ async function requestAuthorization() {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/whatsapp/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/whatsapp/Reauthorize.vue
index 229b62f69..da6cc66bf 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/whatsapp/Reauthorize.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/whatsapp/Reauthorize.vue
@@ -201,7 +201,7 @@ defineExpose({
STATUS_COLORS[status] || 'text-n-slate-12';
-
+
-
+
{{ t('INBOX_MGMT.ACCOUNT_HEALTH.TITLE') }}
-
+
{{ t('INBOX_MGMT.ACCOUNT_HEALTH.DESCRIPTION') }}
@@ -169,7 +169,7 @@ const getStatusTextColor = status => STATUS_COLORS[status] || 'text-n-slate-12';
class="flex flex-col gap-2 p-4 rounded-lg border border-n-weak bg-n-solid-1"
>
-
+
{{ item.label }}
STATUS_COLORS[status] || 'text-n-slate-12';
{{ item.value }}
{{ formatStatusDisplay(item.value) }}
{{ formatModeDisplay(item.value) }}
{{ formatTierDisplay(item.value) }}
- {{
+ {{
item.value
}}
@@ -219,7 +219,9 @@ const getStatusTextColor = status => STATUS_COLORS[status] || 'text-n-slate-12';
>
-
{{ t('INBOX_MGMT.ACCOUNT_HEALTH.NO_DATA') }}
+
+ {{ t('INBOX_MGMT.ACCOUNT_HEALTH.NO_DATA') }}
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BotConfiguration.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BotConfiguration.vue
index 6cd318d9b..80e1497de 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BotConfiguration.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BotConfiguration.vue
@@ -1,15 +1,17 @@
-
+
-
-
+
-
-
-
-
- {{ $t('AGENT_BOTS.BOT_CONFIGURATION.SELECT_PLACEHOLDER') }}
-
-
+
+
+
+
+
+
- {{ agentBot.name }}
-
-
-
-
-
-
- {{ $t('AGENT_BOTS.BOT_CONFIGURATION.DISCONNECT') }}
-
+ {{ $t('AGENT_BOTS.BOT_CONFIGURATION.DISCONNECT') }}
+
+
-
-
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BusinessDay.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BusinessDay.vue
index e8b28870c..be4d1d30e 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BusinessDay.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/BusinessDay.vue
@@ -2,11 +2,26 @@
import parse from 'date-fns/parse';
import differenceInMinutes from 'date-fns/differenceInMinutes';
import { generateTimeSlots } from '../helpers/businessHour';
+import Icon from 'dashboard/components-next/icon/Icon.vue';
+import NextSelect from 'dashboard/components-next/select/Select.vue';
const timeSlots = generateTimeSlots(30);
+const groupByPeriod = slots =>
+ ['AM', 'PM']
+ .map(period => ({
+ label: period,
+ options: slots
+ .filter(s => s.endsWith(period))
+ .map(s => ({ value: s, label: s })),
+ }))
+ .filter(g => g.options.length);
+
export default {
- components: {},
+ components: {
+ Icon,
+ NextSelect,
+ },
props: {
dayName: {
type: String,
@@ -23,12 +38,10 @@ export default {
emits: ['update'],
computed: {
fromTimeSlots() {
- return timeSlots;
+ return groupByPeriod(timeSlots);
},
toTimeSlots() {
- return timeSlots.filter(slot => {
- return slot !== '12:00 AM';
- });
+ return groupByPeriod(timeSlots.filter(slot => slot !== '12:00 AM'));
},
isDayEnabled: {
get() {
@@ -135,99 +148,67 @@ export default {
-
-
-
-
-
- {{ dayName }}
-
-
-
-
-
+
+
+
+
+ {{ dayName }}
+
+
+
+
+
+
+
+
+ {{
+ $t('INBOX_MGMT.BUSINESS_HOURS.ALL_DAY')
+ }}
+
+
+
+
+
+
-
{{
- $t('INBOX_MGMT.BUSINESS_HOURS.ALL_DAY')
- }}
-
-
-
-
-
+
+ {{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.VALIDATION_ERROR') }}
+
-
- {{
- $t('INBOX_MGMT.BUSINESS_HOURS.DAY.VALIDATION_ERROR')
- }}
-
-
-
-
+
{{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.UNAVAILABLE') }}
-
-
+
+
{{ totalHours }}
-
-
+
+
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/SenderNameExamplePreview.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/SenderNameExamplePreview.vue
index 6e5d2c251..90fa47768 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/SenderNameExamplePreview.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/SenderNameExamplePreview.vue
@@ -1,114 +1,98 @@
-
-
-
+
-
-
-
- {{ $t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FOR_EG') }}
-
-
-
-
-
-
- {{ keyOption.preview.senderName }}
-
-
- {{ $t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FRIENDLY.FROM') }}
-
-
- {{ businessName || keyOption.preview.businessName }}
-
-
-
{{ keyOption.preview.email }}
-
+
+
+
+
+
+ {{ keyOption.preview.senderName }}
+
+
+ {{ t('INBOX_MGMT.EDIT.SENDER_NAME_SECTION.FRIENDLY.FROM') }}
+
+
+ {{ props.businessName || keyOption.preview.businessName }}
+
+
+ {{ keyOption.preview.email }}
+
-
-
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WeeklyAvailability.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WeeklyAvailability.vue
index 83f2bed8c..a5a43496a 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WeeklyAvailability.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WeeklyAvailability.vue
@@ -2,7 +2,8 @@
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables';
import inboxMixin from 'shared/mixins/inboxMixin';
-import SettingsSection from 'dashboard/components/SettingsSection.vue';
+import SettingsToggleSection from 'dashboard/components-next/Settings/SettingsToggleSection.vue';
+import SettingsFieldSection from 'dashboard/components-next/Settings/SettingsFieldSection.vue';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import BusinessDay from './BusinessDay.vue';
import {
@@ -12,6 +13,7 @@ import {
timeZoneOptions,
} from '../helpers/businessHour';
import NextButton from 'dashboard/components-next/button/Button.vue';
+import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
const DEFAULT_TIMEZONE = {
label: 'Pacific Time (US & Canada) (GMT-07:00)',
@@ -20,10 +22,12 @@ const DEFAULT_TIMEZONE = {
export default {
components: {
- SettingsSection,
+ SettingsToggleSection,
+ SettingsFieldSection,
BusinessDay,
NextButton,
WootMessageEditor,
+ ComboBox,
},
mixins: [inboxMixin],
props: {
@@ -58,6 +62,15 @@ export default {
timeZones() {
return [...timeZoneOptions()];
},
+ timeZoneValue: {
+ get() {
+ return this.timeZone.value;
+ },
+ set(value) {
+ const match = this.timeZones.find(tz => tz.value === value);
+ if (match) this.timeZone = match;
+ },
+ },
isRichEditorEnabled() {
if (
this.isATwilioChannel ||
@@ -121,96 +134,104 @@ export default {
-
-
+
-
-
-
- {{ $t('INBOX_MGMT.BUSINESS_HOURS.TOGGLE_AVAILABILITY') }}
-
-
- {{ $t('INBOX_MGMT.BUSINESS_HOURS.TOGGLE_HELP') }}
-
-
-
-
- {{ $t('INBOX_MGMT.BUSINESS_HOURS.UNAVAILABLE_MESSAGE_LABEL') }}
-
-
-
-
-
-
-
-
- {{ $t('INBOX_MGMT.BUSINESS_HOURS.TIMEZONE_LABEL') }}
-
-
-
-
-
- {{ $t('INBOX_MGMT.BUSINESS_HOURS.WEEKLY_TITLE') }}
-
-
onSlotUpdate(timeSlot.day, data)"
+
+
+
+
+
+
+
+
+
+
+ {{ $t('INBOX_MGMT.BUSINESS_HOURS.WEEKLY_TITLE') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.DAY') }}
+
+
+ {{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.AVAILABILITY') }}
+
+
+ {{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.HOURS') }}
+
+
+
+
+ onSlotUpdate(timeSlot.day, data)"
+ />
+
+
+
+
+
-
-
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue
index c234cb7ae..dd566f131 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/facebook/Reauthorize.vue
@@ -99,7 +99,7 @@ export default {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/teams/Create/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/teams/Create/Index.vue
index 6136bb354..f7777291c 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/teams/Create/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/teams/Create/Index.vue
@@ -23,9 +23,9 @@ export default {
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/teams/Edit/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/teams/Edit/Index.vue
index ee5f4366f..fb179f3a7 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/teams/Edit/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/teams/Edit/Index.vue
@@ -27,9 +27,9 @@ export default {
-
+
getters['teams/getTeams'].value);
-const uiFlags = computed(() => getters['teams/getUIFlags'].value);
const { isAdmin } = useAdmin();
const loading = ref({});
+const searchQuery = ref('');
+
+const teamsList = useMapGetter('teams/getTeams');
+
+const filteredTeamsList = computed(() => {
+ const query = searchQuery.value.trim();
+ if (!query) return teamsList.value;
+ return picoSearch(teamsList.value, query, ['name', 'description']);
+});
+
+const uiFlags = computed(() => getters['teams/getUIFlags'].value);
const deleteTeam = async ({ id }) => {
try {
@@ -68,74 +80,94 @@ const confirmPlaceHolderText = computed(() =>
-
-
-
-
-
-
-
-
-
-
-
+
+
- {{ $t('TEAMS_SETTINGS.LIST.404') }}
-
-
-
-
-
-
- {{ team.name }}
- {{ team.description }}
-
-
-
-
-
-
+
+
+ {{ $t('TEAMS_SETTINGS.COUNT', { n: teamsList.length }) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('TEAMS_SETTINGS.NO_RESULTS') }}
+
+
+
+
+
+
+
+
+
+ {{ team.name }}
+
+
+ {{ team.description }}
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
@on-confirm="confirmDeletion"
@on-close="closeDelete"
/>
-
+
diff --git a/app/javascript/shared/components/GreetingsEditor.vue b/app/javascript/shared/components/GreetingsEditor.vue
index a94b4caef..74f9e149a 100644
--- a/app/javascript/shared/components/GreetingsEditor.vue
+++ b/app/javascript/shared/components/GreetingsEditor.vue
@@ -20,10 +20,7 @@ const greetingsMessage = computed({
-
+
`,
+ width: 16,
+ height: 16,
+ },
+ 'edit-pen': {
+ body: ` `,
+ width: 16,
+ height: 16,
+ },
+ settings: {
+ body: ` `,
+ width: 16,
+ height: 16,
+ },
+ clone: {
+ body: ` `,
+ width: 16,
+ height: 16,
+ },
+ 'sort-ascending': {
+ body: ` `,
+ width: 24,
+ height: 24,
+ },
+ 'sort-descending': {
+ body: ` `,
+ width: 24,
+ height: 24,
+ },
+ 'drag-indicator': {
+ body: ` `,
+ width: 7,
+ height: 11,
+ },
+
/** Channels Starts */
website: {
- body: ` `,
- width: 16,
- height: 16,
+ body: ` `,
+ width: 24,
+ height: 24,
},
line: {
- body: ` `,
- width: 13,
- height: 13,
+ body: ` `,
+ width: 24,
+ height: 24,
},
facebook: {
- body: ` `,
- width: 14,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
},
whatsapp: {
- body: ` `,
- width: 14,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
},
instagram: {
- body: ` `,
- width: 12,
- height: 12,
+ body: ` `,
+ width: 24,
+ height: 24,
},
tiktok: {
- body: ` `,
- width: 12,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
},
messenger: {
- body: ` `,
- width: 14,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
},
mail: {
- body: ` `,
- width: 14,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
},
sms: {
- body: ` `,
- width: 14,
- height: 12,
+ body: ` `,
+ width: 24,
+ height: 24,
},
telegram: {
- body: ` `,
- width: 14,
- height: 12,
+ body: ` `,
+ width: 24,
+ height: 24,
},
api: {
- body: ` `,
- width: 15,
- height: 12,
+ body: ` `,
+ width: 24,
+ height: 24,
},
twilio: {
- body: ` `,
- width: 16,
- height: 16,
+ body: ` `,
+ width: 24,
+ height: 24,
},
gmail: {
- body: ` `,
- width: 14,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
},
outlook: {
- body: ` `,
- width: 14,
- height: 14,
+ body: ` `,
+ width: 24,
+ height: 24,
+ },
+ voice: {
+ body: ` `,
+ width: 24,
+ height: 24,
+ },
+ github: {
+ body: ` `,
+ width: 24,
+ height: 24,
+ },
+ x: {
+ body: ` `,
+ width: 24,
+ height: 24,
+ },
+ linkedin: {
+ body: ` `,
+ width: 24,
+ height: 24,
},
gemini: {
width: 32,
From 6902969a09864ee250cf0e1f326b2ca53cb1cfa1 Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Thu, 19 Feb 2026 15:42:34 +0530
Subject: [PATCH 005/113] chore: Remove `vue-multiselect` package and styles
from codebase (#13585)
---
app/javascript/dashboard/App.vue | 6 -
.../dashboard/assets/scss/_base.scss | 15 +-
.../dashboard/assets/scss/_woot.scss | 1 -
.../assets/scss/plugins/_multiselect.scss | 273 ------------------
.../dashboard/conversation/ContactPanel.vue | 12 -
.../conversation/ConversationAction.vue | 6 +-
.../reports/components/ReportsWrapper.vue | 78 +----
app/javascript/entrypoints/dashboard.js | 2 -
package.json | 1 -
pnpm-lock.yaml | 9 -
10 files changed, 7 insertions(+), 396 deletions(-)
delete mode 100644 app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue
index 61d7631e7..8912c03d1 100644
--- a/app/javascript/dashboard/App.vue
+++ b/app/javascript/dashboard/App.vue
@@ -164,10 +164,4 @@ export default {
.v-popper--theme-tooltip .v-popper__arrow-container {
display: none;
}
-
-.multiselect__input {
- margin-bottom: 0px !important;
-}
-
-
diff --git a/app/javascript/dashboard/assets/scss/_base.scss b/app/javascript/dashboard/assets/scss/_base.scss
index d3d3f6726..84c8a4b0f 100644
--- a/app/javascript/dashboard/assets/scss/_base.scss
+++ b/app/javascript/dashboard/assets/scss/_base.scss
@@ -78,7 +78,7 @@ textarea {
}
}
-$form-input-selector: "input[type]:not([type='file']):not([type='checkbox']):not([type='radio']):not([type='range']):not([type='button']):not([type='submit']):not([type='reset']):not([type='color']):not([type='image']):not([type='hidden']):not(.reset-base):not(.multiselect__input):not(.no-margin)";
+$form-input-selector: "input[type]:not([type='file']):not([type='checkbox']):not([type='radio']):not([type='range']):not([type='button']):not([type='submit']):not([type='reset']):not([type='color']):not([type='image']):not([type='hidden']):not(.reset-base):not(.no-margin)";
#{$form-input-selector} {
@apply field-base h-10;
@@ -92,7 +92,7 @@ $form-input-selector: "input[type]:not([type='file']):not([type='checkbox']):not
}
}
-input[type='file']:not(.multiselect__input) {
+input[type='file'] {
@apply leading-[1.15] mb-4 border-0 bg-transparent text-sm;
}
@@ -126,13 +126,6 @@ label:has(.help-text) {
}
}
-// Error handling
-.has-multi-select-error {
- div.multiselect {
- @apply mb-1;
- }
-}
-
// FormKit support
.formkit-outer[data-invalid='true'] {
#{$form-input-selector},
@@ -150,9 +143,7 @@ label:has(.help-text) {
#{$form-input-selector},
input:not([type]),
textarea,
- select,
- .multiselect > .multiselect__tags,
- .multiselect:not(.no-margin) {
+ select {
@apply field-error;
}
diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss
index 81a4569b5..27764d150 100644
--- a/app/javascript/dashboard/assets/scss/_woot.scss
+++ b/app/javascript/dashboard/assets/scss/_woot.scss
@@ -13,7 +13,6 @@
@import 'base';
// Plugins
-@import 'plugins/multiselect';
@import 'plugins/date-picker';
html,
diff --git a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
deleted file mode 100644
index 9567d66c3..000000000
--- a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
+++ /dev/null
@@ -1,273 +0,0 @@
-@mixin label-multiselect-hover {
- &::after {
- @apply text-n-brand;
- }
-
- &:hover {
- @apply bg-n-slate-3;
-
- &::after {
- @apply text-n-blue-11;
- }
- }
-}
-
-.multiselect {
- &:not(.no-margin) {
- @apply mb-4;
- }
-
- &.invalid .multiselect__tags {
- @apply border-0 outline outline-1 outline-n-ruby-8 dark:outline-n-ruby-8 hover:outline-n-ruby-9 dark:hover:outline-n-ruby-9 disabled:outline-n-ruby-8 dark:disabled:outline-n-ruby-8;
- }
-
- &.multiselect--disabled {
- @apply opacity-50 rounded-lg cursor-not-allowed pointer-events-auto;
-
- .multiselect__select {
- @apply cursor-not-allowed bg-transparent rounded-lg;
- }
- }
-
- .multiselect--active {
- > .multiselect__tags {
- @apply outline-n-blue-border;
- }
- }
-
- .multiselect__select {
- @apply min-h-[2.875rem] p-0 right-0 top-0;
-
- &::before {
- @apply right-0;
- }
- }
-
- .multiselect__content-wrapper {
- @apply bg-n-alpha-black2 text-n-slate-12 backdrop-blur-[100px] border-0 border-none outline outline-1 outline-n-weak rounded-b-lg;
- }
-
- .multiselect__content {
- @apply max-w-full;
-
- .multiselect__option {
- @apply text-sm font-normal flex justify-between items-center;
-
- span {
- @apply inline-block overflow-hidden text-ellipsis whitespace-nowrap w-fit;
- }
-
- p {
- @apply mb-0;
- }
-
- &::after {
- @apply bottom-0 flex items-center justify-center text-center relative px-1 leading-tight;
- }
-
- &.multiselect__option--highlight {
- @apply bg-n-alpha-black2 text-n-slate-12;
- }
-
- &.multiselect__option--highlight:hover {
- @apply bg-n-brand/10 text-n-slate-12;
-
- &::after {
- @apply bg-transparent text-center text-n-slate-12;
- }
- }
-
- &.multiselect__option--highlight::after {
- @apply bg-transparent text-n-slate-12;
- }
-
- &.multiselect__option--selected {
- @apply bg-n-brand/20 text-n-slate-12;
-
- &::after {
- @apply bg-transparent;
- }
-
- &.multiselect__option--highlight:hover {
- @apply bg-transparent;
-
- &::after:hover {
- @apply text-n-slate-12 bg-transparent;
- }
- }
- }
- }
- }
-
- .multiselect__tags {
- @apply bg-n-alpha-black2 border-0 grid items-center w-full border-none outline-1 outline outline-n-weak hover:outline-n-slate-6 m-0 min-h-[2.875rem] rounded-lg pt-0;
-
- input {
- @apply border-0 border-none bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-10;
- }
- }
-
- .multiselect__spinner {
- background-color: transparent;
- }
-
- .multiselect__tags-wrap {
- @apply inline-block leading-none mt-1;
- }
-
- .multiselect__placeholder {
- @apply text-n-slate-10 font-normal pt-3;
- }
-
- .multiselect__tag {
- @apply bg-n-alpha-white mt-1 text-n-slate-12 pr-6 pl-2.5 py-1.5;
- }
-
- .multiselect__tag-icon {
- @include label-multiselect-hover;
- }
-
- .multiselect__input {
- @apply text-sm h-[2.875rem] mb-0 p-0 shadow-none border-transparent hover:border-transparent hover:shadow-none focus:border-transparent focus:shadow-none active:border-transparent active:shadow-none;
- }
-
- .multiselect__single {
- @apply bg-transparent text-n-slate-12 inline-block mb-0 py-3 px-2.5 overflow-hidden whitespace-nowrap text-ellipsis;
- }
-}
-
-.sidebar-labels-wrap {
- &.has-edited,
- &:hover {
- .multiselect {
- @apply cursor-pointer;
- }
- }
-
- .multiselect {
- > .multiselect__select {
- @apply invisible;
- }
-
- > .multiselect__tags {
- @apply outline-transparent;
- }
-
- &.multiselect--active > .multiselect__tags {
- @apply outline-n-blue-border;
- }
- }
-}
-
-.multiselect-wrap--small {
- // To be removed one SLA reports date picker is created
- &.tiny {
- .multiselect.no-margin {
- @apply min-h-[32px];
- }
-
- .multiselect__select {
- @apply min-h-[32px] h-8;
-
- &::before {
- @apply top-[60%];
- }
- }
-
- .multiselect__tags {
- @apply min-h-[32px] max-h-[32px];
-
- .multiselect__single {
- @apply pt-1 pb-1;
- }
- }
- }
-
- .multiselect__tags,
- .multiselect__input,
- .multiselect {
- @apply text-n-slate-12 rounded-lg text-sm min-h-[2.5rem];
- }
-
- .multiselect__input {
- @apply h-[2.375rem] min-h-[2.375rem];
- }
-
- .multiselect__single {
- @apply items-center flex m-0 text-sm max-h-[2.375rem] bg-transparent text-n-slate-12 py-3 px-0.5;
- }
-
- .multiselect__placeholder {
- @apply m-0 py-2 px-0.5;
- }
-
- .multiselect__tag {
- @apply py-[6px] my-[1px];
- }
-
- .multiselect__select {
- @apply min-h-[2.5rem];
- }
-
- .multiselect--disabled .multiselect__current,
- .multiselect--disabled .multiselect__select {
- @apply bg-transparent;
- }
-}
-
-.multiselect--disabled {
- background-color: rgba(var(--black-alpha-2)) !important;
-
- .multiselect__tags {
- @apply hover:outline-n-weak;
- }
-}
-
-.multiselect--active {
- .multiselect__select::before {
- @apply top-[62%];
- }
-}
-
-.multiselect__select::before {
- top: 60% !important;
-}
-
-.multiselect-wrap--medium {
- .multiselect__tags,
- .multiselect__input {
- @apply items-center flex;
- }
-
- .multiselect__tags,
- .multiselect__input,
- .multiselect {
- @apply bg-n-alpha-black2 text-n-slate-12 text-sm h-12 min-h-[3rem];
- }
-
- .multiselect__input {
- @apply h-[2.875rem] min-h-[2.875rem];
- margin-bottom: 0 !important;
- }
-
- .multiselect__single {
- @apply items-center flex m-0 text-sm py-1 px-0.5 bg-transparent text-n-slate-12;
- }
-
- .multiselect__placeholder {
- @apply m-0 py-1 px-0.5;
- }
-
- .multiselect__select {
- @apply min-h-[3rem];
- }
-
- .multiselect--disabled .multiselect__current,
- .multiselect--disabled .multiselect__select {
- @apply bg-transparent;
- }
-
- .multiselect__tags-wrap {
- @apply flex-shrink-0;
- }
-}
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue
index b163bfdc8..653b43840 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue
@@ -308,17 +308,5 @@ onMounted(() => {
.contact--profile {
@apply pb-3 border-b border-solid border-n-weak;
}
-
- .conversation--actions .multiselect-wrap--small {
- .multiselect {
- @apply box-border pl-6;
- }
-
- .multiselect__element {
- span {
- @apply w-full;
- }
- }
- }
}
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue b/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue
index 35d985c77..4c14ba2ab 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/ConversationAction.vue
@@ -211,7 +211,7 @@ export default {
-
+
-
+
-
+
-
+
-
-
diff --git a/app/javascript/entrypoints/dashboard.js b/app/javascript/entrypoints/dashboard.js
index cd2cda6bd..d0c6c808d 100644
--- a/app/javascript/entrypoints/dashboard.js
+++ b/app/javascript/entrypoints/dashboard.js
@@ -5,7 +5,6 @@ import axios from 'axios';
// Global Components
import hljsVuePlugin from '@highlightjs/vue-plugin';
-import Multiselect from 'vue-multiselect';
import { plugin, defaultConfig } from '@formkit/vue';
import WootWizard from 'components/ui/Wizard.vue';
import FloatingVue from 'floating-vue';
@@ -92,7 +91,6 @@ app.use(FloatingVue, {
});
app.use(hljsVuePlugin);
-app.component('multiselect', Multiselect);
app.component('woot-wizard', WootWizard);
app.component('fluent-icon', FluentIcon);
diff --git a/package.json b/package.json
index b062495a0..e36b2f53f 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,6 @@
"vue-dompurify-html": "^5.1.0",
"vue-i18n": "9.14.5",
"vue-letter": "^0.2.1",
- "vue-multiselect": "3.1.0",
"vue-router": "~4.4.5",
"vue-upload-component": "^3.1.17",
"vue-virtual-scroller": "^2.0.0-beta.8",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7a1b6f35f..e880b4aaf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -223,9 +223,6 @@ importers:
vue-letter:
specifier: ^0.2.1
version: 0.2.1
- vue-multiselect:
- specifier: 3.1.0
- version: 3.1.0
vue-router:
specifier: ~4.4.5
version: 4.4.5(vue@3.5.12(typescript@5.6.2))
@@ -4628,10 +4625,6 @@ packages:
vue-letter@0.2.1:
resolution: {integrity: sha512-IYWp47XUikjKfEniWYlFxeJFKABZwAE5IEjz866qCBytBr2dzqVDdjoMDpBP//krxkzN/QZYyHe6C09y/IODYg==}
- vue-multiselect@3.1.0:
- resolution: {integrity: sha512-+i/fjTqFBpaay9NP+lU7obBeNaw2DdFDFs4mqhsM0aEtKRdvIf7CfREAx2o2B4XDmPrBt1r7x1YCM3BOMLaUgQ==}
- engines: {node: '>= 14.18.1', npm: '>= 6.14.15'}
-
vue-observe-visibility@2.0.0-alpha.1:
resolution: {integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==}
peerDependencies:
@@ -9699,8 +9692,6 @@ snapshots:
dependencies:
lettersanitizer: 1.0.6
- vue-multiselect@3.1.0: {}
-
vue-observe-visibility@2.0.0-alpha.1(vue@3.5.12(typescript@5.6.2)):
dependencies:
vue: 3.5.12(typescript@5.6.2)
From f826dc2d15d7ec407e23770ccf8279f11473e749 Mon Sep 17 00:00:00 2001
From: Pranav
Date: Thu, 19 Feb 2026 17:48:06 -0800
Subject: [PATCH 006/113] fix: Rate-limit meta endpoint calls to 30/min
(#13596)
Meta endpoints are now rate limited to 1 call per every minute. This rate limit is done at the user level not the browser.
---
config/initializers/rack_attack.rb | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index 1f500243a..b193c2e14 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -221,6 +221,19 @@ class Rack::Attack
match_data[:account_id] if match_data.present?
end
+ ## Prevent increased use of conversations meta API per user
+ throttle('/api/v1/accounts/:account_id/conversations/meta/user',
+ limit: ENV.fetch('RATE_LIMIT_CONVERSATIONS_META', '30').to_i, period: 1.minute) do |req|
+ match_data = %r{/api/v1/accounts/(?\d+)/conversations/meta}.match(req.path)
+ next unless match_data.present? && req.get?
+
+ user_uid = req.get_header('HTTP_UID')
+ api_access_token = req.get_header('HTTP_API_ACCESS_TOKEN') || req.get_header('api_access_token')
+ user_identifier = user_uid.presence || api_access_token.presence
+
+ "#{user_identifier}:#{match_data[:account_id]}" if user_identifier.present?
+ end
+
## ----------------------------------------------- ##
end
From 26c38a90f2c2bfb371087862818f0c0a255e4643 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 Feb 2026 17:55:08 -0800
Subject: [PATCH 007/113] chore(deps): bump nokogiri from 1.18.9 to 1.19.1
(#13586)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.18.9
to 1.19.1.
Release notes
Sourced from nokogiri's
releases .
v1.19.1 / 2026-02-16
Security
[CRuby] Address unchecked return value from
xmlC14NExecute which was a contributing cause to ruby-saml
GHSA-x4h9-gwv3-r4m4. See GHSA-wx95-c6cv-8532
for more information.
cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32
nokogiri-1.19.1-aarch64-linux-gnu.gem
1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5
nokogiri-1.19.1-aarch64-linux-musl.gem
0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3
nokogiri-1.19.1-arm-linux-gnu.gem
3a18e559ee499b064aac6562d98daab3d39ba6cbb4074a1542781b2f556db47d
nokogiri-1.19.1-arm-linux-musl.gem
dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e
nokogiri-1.19.1-arm64-darwin.gem
1e0bda88b1c6409f0edb9e0c25f1bf9ff4fa94c3958f492a10fcf50dda594365
nokogiri-1.19.1-java.gem
110d92ae57694ae7866670d298a5d04cd150fae5a6a7849957d66f171e6aec9b
nokogiri-1.19.1-x64-mingw-ucrt.gem
7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf
nokogiri-1.19.1-x86_64-darwin.gem
1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a
nokogiri-1.19.1-x86_64-linux-gnu.gem
4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23
nokogiri-1.19.1-x86_64-linux-musl.gem
598b327f36df0b172abd57b68b18979a6e14219353bca87180c31a51a00d5ad3
nokogiri-1.19.1.gem
v1.19.0 / 2025-12-28
Ruby
This release is focused on changes to Ruby version support, and is
otherwise functionally identical to v1.18.10.
11a97ecc3c0e7e5edcf395720b10860ef493b768f6aa80c539573530bc933767
nokogiri-1.19.0-aarch64-linux-gnu.gem
eb70507f5e01bc23dad9b8dbec2b36ad0e61d227b42d292835020ff754fb7ba9
nokogiri-1.19.0-aarch64-linux-musl.gem
572a259026b2c8b7c161fdb6469fa2d0edd2b61cd599db4bbda93289abefbfe5
nokogiri-1.19.0-arm-linux-gnu.gem
23ed90922f1a38aed555d3de4d058e90850c731c5b756d191b3dc8055948e73c
nokogiri-1.19.0-arm-linux-musl.gem
0811dfd936d5f6dd3f6d32ef790568bf29b2b7bead9ba68866847b33c9cf5810
nokogiri-1.19.0-arm64-darwin.gem
5f3a70e252be641d8a4099f7fb4cc25c81c632cb594eec9b4b8f2ca8be4374f3
nokogiri-1.19.0-java.gem
05d7ed2d95731edc9bef2811522dc396df3e476ef0d9c76793a9fca81cab056b
nokogiri-1.19.0-x64-mingw-ucrt.gem
1dad56220b603a8edb9750cd95798bffa2b8dd9dd9aa47f664009ee5b43e3067
nokogiri-1.19.0-x86_64-darwin.gem
f482b95c713d60031d48c44ce14562f8d2ce31e3a9e8dd0ccb131e9e5a68b58c
nokogiri-1.19.0-x86_64-linux-gnu.gem
1c4ca6b381622420073ce6043443af1d321e8ed93cc18b08e2666e5bd02ffae4
nokogiri-1.19.0-x86_64-linux-musl.gem
e304d21865f62518e04f2bf59f93bd3a97ca7b07e7f03952946d8e1c05f45695
nokogiri-1.19.0.gem
... (truncated)
Changelog
Sourced from nokogiri's
changelog .
v1.19.1 / 2026-02-16
Security
[CRuby] Address unchecked return value from
xmlC14NExecute which was a contributing cause to ruby-saml
GHSA-x4h9-gwv3-r4m4. See GHSA-wx95-c6cv-8532
for more information.
v1.19.0 / 2025-12-28
Ruby
This release is focused on changes to Ruby version support, and is
otherwise functionally identical to v1.18.10.
v1.18.10 / 2025-09-15
Dependencies
[CRuby] Vendored libxml2 is updated to v2.13.9 .
Note that the security fixes published in v2.13.9 were already present
in Nokogiri v1.18.9.
[CRuby] [Windows and MacOS] Vendored libiconv is updated to v1.18
Commits
d913045
version bump to v1.19.1
b81cb98
doc: update CHANGELOG for upcoming v1.19.1
8e66809
C14n raise on failure (#3600 )
5b77f3d
Raise RuntimeError when canonicalization fails
edc5595
Thank sponsors in the README
d4dc245
dep: update rdoc to v7
d77bfb6
version bump to v1.19.0
1eb5c2c
dev: convert scripts/test-gem-set to use mise
88a120f
dep: Add native Ruby 4 support, drop Ruby 3.1 support (v1.19.x) (#3592 )
f8c8f74
Skip the parser compression test for Windows system libs
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Gemfile.lock | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 717ec2a02..aad0438cf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -583,14 +583,14 @@ GEM
newrelic_rpm (9.6.0)
base64
nio4r (2.7.3)
- nokogiri (1.18.9)
+ nokogiri (1.19.1)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- nokogiri (1.18.9-arm64-darwin)
+ nokogiri (1.19.1-arm64-darwin)
racc (~> 1.4)
- nokogiri (1.18.9-x86_64-darwin)
+ nokogiri (1.19.1-x86_64-darwin)
racc (~> 1.4)
- nokogiri (1.18.9-x86_64-linux-gnu)
+ nokogiri (1.19.1-x86_64-linux-gnu)
racc (~> 1.4)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
From dbab0fe8da3b831d7c13cbdb9176faadc93eff63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nat=C3=A3?= <66445721+dollyzn@users.noreply.github.com>
Date: Fri, 20 Feb 2026 02:54:37 -0300
Subject: [PATCH 008/113] fix: search header overlap with new conversation form
(#13548)
---
app/javascript/dashboard/components/ChannelSelector.vue | 2 +-
.../dashboard/modules/search/components/SearchResultSection.vue | 2 +-
.../dashboard/contacts/components/ContactsBulkActionBar.vue | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/javascript/dashboard/components/ChannelSelector.vue b/app/javascript/dashboard/components/ChannelSelector.vue
index 9e593af51..2e3ea7d86 100644
--- a/app/javascript/dashboard/components/ChannelSelector.vue
+++ b/app/javascript/dashboard/components/ChannelSelector.vue
@@ -47,7 +47,7 @@ defineProps({
{{ $t('CHANNEL_SELECTOR.COMING_SOON') }} 🚀
diff --git a/app/javascript/dashboard/modules/search/components/SearchResultSection.vue b/app/javascript/dashboard/modules/search/components/SearchResultSection.vue
index 40d1ca6c6..77a7f3bd3 100644
--- a/app/javascript/dashboard/modules/search/components/SearchResultSection.vue
+++ b/app/javascript/dashboard/modules/search/components/SearchResultSection.vue
@@ -33,7 +33,7 @@ const titleCase = computed(() => props.title.toLowerCase());
{{ title }}
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue
index e8ddd5223..e56a2dda7 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue
@@ -94,7 +94,7 @@ const handleAssignLabels = labels => {
Date: Fri, 20 Feb 2026 12:16:43 +0530
Subject: [PATCH 009/113] feat: captain channel type langfuse metadata (#13574)
# Pull Request Template
## Description
Adds channel type to Captain assistant traces in Langfuse
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Claude Opus 4.6
---
enterprise/app/helpers/captain/chat_helper.rb | 20 +++++++++----------
.../captain/assistant/agent_runner_service.rb | 4 +++-
.../assistant/agent_runner_service_spec.rb | 1 +
.../llm/assistant_chat_service_spec.rb | 13 ++++++++++++
4 files changed, 27 insertions(+), 11 deletions(-)
diff --git a/enterprise/app/helpers/captain/chat_helper.rb b/enterprise/app/helpers/captain/chat_helper.rb
index 2734e510e..d5ac6df33 100644
--- a/enterprise/app/helpers/captain/chat_helper.rb
+++ b/enterprise/app/helpers/captain/chat_helper.rb
@@ -53,15 +53,13 @@ module Captain::ChatHelper
chat.on_end_message { |message| record_llm_generation(chat, message) }
chat.on_tool_call { |tool_call| handle_tool_call(tool_call) }
chat.on_tool_result { |result| handle_tool_result(result) }
-
chat
end
def handle_tool_call(tool_call)
persist_thinking_message(tool_call)
start_tool_span(tool_call)
- @pending_tool_calls ||= []
- @pending_tool_calls.push(tool_call)
+ (@pending_tool_calls ||= []).push(tool_call)
end
def handle_tool_result(result)
@@ -87,8 +85,9 @@ module Captain::ChatHelper
messages: chat ? chat.messages.map { |m| { role: m.role.to_s, content: m.content.to_s } } : @messages,
temperature: temperature,
metadata: {
- assistant_id: @assistant&.id
- }
+ assistant_id: @assistant&.id,
+ channel_type: resolved_channel_type
+ }.compact
}
end
@@ -104,6 +103,10 @@ module Captain::ChatHelper
@account&.id || @assistant&.account_id
end
+ def resolved_channel_type
+ Conversation.find_by(account_id: resolved_account_id, display_id: @conversation_id)&.inbox&.channel_type if @conversation_id
+ end
+
# Ensures all LLM calls and tool executions within an agentic loop
# are grouped under a single trace/session in Langfuse.
#
@@ -127,10 +130,7 @@ module Captain::ChatHelper
end
def log_chat_completion_request
- Rails.logger.info(
- "#{self.class.name} Assistant: #{@assistant.id}, Requesting chat completion
- for messages #{@messages} with #{@tools&.length || 0} tools
- "
- )
+ Rails.logger.info("#{self.class.name} Assistant: #{@assistant.id}, Requesting chat completion " \
+ "for messages #{@messages} with #{@tools&.length || 0} tools")
end
end
diff --git a/enterprise/app/services/captain/assistant/agent_runner_service.rb b/enterprise/app/services/captain/assistant/agent_runner_service.rb
index c832da93e..9aeee605f 100644
--- a/enterprise/app/services/captain/assistant/agent_runner_service.rb
+++ b/enterprise/app/services/captain/assistant/agent_runner_service.rb
@@ -114,6 +114,7 @@ class Captain::Assistant::AgentRunnerService
if @conversation
state[:conversation] = @conversation.attributes.symbolize_keys.slice(*CONVERSATION_STATE_ATTRIBUTES)
+ state[:channel_type] = @conversation.inbox&.channel_type
state[:contact] = @conversation.contact.attributes.symbolize_keys.slice(*CONTACT_STATE_ATTRIBUTES) if @conversation.contact
end
@@ -151,7 +152,8 @@ class Captain::Assistant::AgentRunnerService
ATTR_LANGFUSE_USER_ID => state[:account_id],
format(ATTR_LANGFUSE_METADATA, 'assistant_id') => state[:assistant_id],
format(ATTR_LANGFUSE_METADATA, 'conversation_id') => conversation[:id],
- format(ATTR_LANGFUSE_METADATA, 'conversation_display_id') => conversation[:display_id]
+ format(ATTR_LANGFUSE_METADATA, 'conversation_display_id') => conversation[:display_id],
+ format(ATTR_LANGFUSE_METADATA, 'channel_type') => state[:channel_type]
}.compact.transform_values(&:to_s)
end
diff --git a/spec/enterprise/services/captain/assistant/agent_runner_service_spec.rb b/spec/enterprise/services/captain/assistant/agent_runner_service_spec.rb
index bbf712622..c3019b82c 100644
--- a/spec/enterprise/services/captain/assistant/agent_runner_service_spec.rb
+++ b/spec/enterprise/services/captain/assistant/agent_runner_service_spec.rb
@@ -278,6 +278,7 @@ RSpec.describe Captain::Assistant::AgentRunnerService do
contact_id: contact.id,
status: conversation.status
)
+ expect(state[:channel_type]).to eq(inbox.channel_type)
end
it 'includes contact attributes when contact is present' do
diff --git a/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb b/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb
index bc4ff0b63..4711e5f7e 100644
--- a/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb
+++ b/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb
@@ -28,6 +28,19 @@ RSpec.describe Captain::Llm::AssistantChatService do
allow(mock_chat).to receive(:messages).and_return([])
end
+ describe 'instrumentation metadata' do
+ it 'passes channel_type to the agent session instrumentation' do
+ service = described_class.new(assistant: assistant, conversation_id: conversation.display_id)
+
+ expect(service).to receive(:instrument_agent_session).with(
+ hash_including(metadata: hash_including(channel_type: conversation.inbox.channel_type))
+ ).and_yield
+
+ allow(mock_chat).to receive(:ask).and_return(mock_response)
+ service.generate_response(message_history: [{ role: 'user', content: 'Hello' }])
+ end
+ end
+
describe 'image analysis' do
context 'when user sends a message with an image attachment' do
let(:message_history) do
From d8f4bb940e522191f57678b853593bd2c352f43f Mon Sep 17 00:00:00 2001
From: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
Date: Fri, 20 Feb 2026 19:08:36 +0530
Subject: [PATCH 010/113] feat: add resolve_conversation tool for Captain V2
scenarios (#13597)
# Pull Request Template
## Description
Adds a new built-in tool that allows Captain scenarios to resolve
conversations programmatically. This enables automated workflows like
the misdirected contact deflector to close conversations after handling
them, while still allowing human review via label filtering.
## Type of change
Please delete options that are not relevant.
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
tested by mentioning it to be used in captain v2 scenario
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Claude Opus 4.6
Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com>
---
config/agents/tools.yml | 5 ++
config/locales/en.yml | 1 +
.../enterprise/activity_message_handler.rb | 35 +++++++------
.../tools/resolve_conversation_tool.rb | 27 ++++++++++
lib/current.rb | 2 +
.../tools/resolve_conversation_tool_spec.rb | 52 +++++++++++++++++++
6 files changed, 105 insertions(+), 17 deletions(-)
create mode 100644 enterprise/lib/captain/tools/resolve_conversation_tool.rb
create mode 100644 spec/enterprise/lib/captain/tools/resolve_conversation_tool_spec.rb
diff --git a/config/agents/tools.yml b/config/agents/tools.yml
index c2faf75e7..ff4f7d28f 100644
--- a/config/agents/tools.yml
+++ b/config/agents/tools.yml
@@ -30,6 +30,11 @@
description: 'Search FAQ responses using semantic similarity'
icon: 'search'
+- id: resolve_conversation
+ title: 'Resolve Conversation'
+ description: 'Resolve a conversation when the issue has been addressed'
+ icon: 'checkmark'
+
- id: handoff
title: 'Handoff to Human'
description: 'Hand off the conversation to a human agent'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 07d9b0e2f..a058d28c7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -229,6 +229,7 @@ en:
activity:
captain:
resolved: 'Conversation was marked resolved by %{user_name} due to inactivity'
+ resolved_by_tool: 'Conversation was marked resolved by %{user_name}: %{reason}'
open: 'Conversation was marked open by %{user_name}'
agent_bot:
error_moved_to_open: 'Conversation was marked open by system due to an error with the agent bot.'
diff --git a/enterprise/app/models/enterprise/activity_message_handler.rb b/enterprise/app/models/enterprise/activity_message_handler.rb
index e6a93718f..66195ec46 100644
--- a/enterprise/app/models/enterprise/activity_message_handler.rb
+++ b/enterprise/app/models/enterprise/activity_message_handler.rb
@@ -1,22 +1,23 @@
module Enterprise::ActivityMessageHandler
def automation_status_change_activity_content
- if Current.executed_by.instance_of?(Captain::Assistant)
- locale = Current.executed_by.account.locale
- if resolved?
- I18n.t(
- 'conversations.activity.captain.resolved',
- user_name: Current.executed_by.name,
- locale: locale
- )
- elsif open?
- I18n.t(
- 'conversations.activity.captain.open',
- user_name: Current.executed_by.name,
- locale: locale
- )
- end
- else
- super
+ return super unless Current.executed_by.instance_of?(Captain::Assistant)
+
+ locale = Current.executed_by.account.locale
+ key = captain_activity_key
+ return unless key
+
+ I18n.t(key, user_name: Current.executed_by.name, reason: Current.captain_resolve_reason, locale: locale)
+ end
+
+ private
+
+ def captain_activity_key
+ if resolved? && Current.captain_resolve_reason.present?
+ 'conversations.activity.captain.resolved_by_tool'
+ elsif resolved?
+ 'conversations.activity.captain.resolved'
+ elsif open?
+ 'conversations.activity.captain.open'
end
end
end
diff --git a/enterprise/lib/captain/tools/resolve_conversation_tool.rb b/enterprise/lib/captain/tools/resolve_conversation_tool.rb
new file mode 100644
index 000000000..0d2563a8b
--- /dev/null
+++ b/enterprise/lib/captain/tools/resolve_conversation_tool.rb
@@ -0,0 +1,27 @@
+class Captain::Tools::ResolveConversationTool < Captain::Tools::BasePublicTool
+ description 'Resolve a conversation when the issue has been addressed or the conversation should be closed'
+ param :reason, type: 'string', desc: 'Brief reason for resolving the conversation', required: true
+
+ def perform(tool_context, reason:)
+ conversation = find_conversation(tool_context.state)
+ return 'Conversation not found' unless conversation
+ return "Conversation ##{conversation.display_id} is already resolved" if conversation.resolved?
+
+ log_tool_usage('resolve_conversation', { conversation_id: conversation.id, reason: reason })
+
+ Current.captain_resolve_reason = reason
+ begin
+ conversation.resolved!
+ ensure
+ Current.captain_resolve_reason = nil
+ end
+
+ "Conversation ##{conversation.display_id} resolved#{" (Reason: #{reason})" if reason}"
+ end
+
+ private
+
+ def permissions
+ %w[conversation_manage conversation_unassigned_manage conversation_participating_manage]
+ end
+end
diff --git a/lib/current.rb b/lib/current.rb
index 3376099f8..5097df369 100644
--- a/lib/current.rb
+++ b/lib/current.rb
@@ -4,6 +4,7 @@ module Current
thread_mattr_accessor :account_user
thread_mattr_accessor :executed_by
thread_mattr_accessor :contact
+ thread_mattr_accessor :captain_resolve_reason
def self.reset
Current.user = nil
@@ -11,5 +12,6 @@ module Current
Current.account_user = nil
Current.executed_by = nil
Current.contact = nil
+ Current.captain_resolve_reason = nil
end
end
diff --git a/spec/enterprise/lib/captain/tools/resolve_conversation_tool_spec.rb b/spec/enterprise/lib/captain/tools/resolve_conversation_tool_spec.rb
new file mode 100644
index 000000000..f91f430e8
--- /dev/null
+++ b/spec/enterprise/lib/captain/tools/resolve_conversation_tool_spec.rb
@@ -0,0 +1,52 @@
+require 'rails_helper'
+
+RSpec.describe Captain::Tools::ResolveConversationTool do
+ let(:account) { create(:account) }
+ let(:assistant) { create(:captain_assistant, account: account) }
+ let(:inbox) { create(:inbox, account: account) }
+ let(:conversation) { create(:conversation, account: account, inbox: inbox, status: :open) }
+ let(:tool) { described_class.new(assistant) }
+ let(:tool_context) { Struct.new(:state).new({ conversation: { id: conversation.id } }) }
+
+ before do
+ Current.executed_by = assistant
+ end
+
+ after do
+ Current.reset
+ end
+
+ describe 'resolving a conversation' do
+ it 'marks resolved and enqueues an activity message with the reason' do
+ tool.perform(tool_context, reason: 'Possible spam')
+
+ expect(conversation.reload).to be_resolved
+ expect(Conversations::ActivityMessageJob).to have_been_enqueued.with(
+ conversation,
+ hash_including(
+ content: I18n.t('conversations.activity.captain.resolved_by_tool', user_name: assistant.name, reason: 'Possible spam')
+ )
+ )
+ end
+
+ it 'clears captain_resolve_reason after execution' do
+ tool.perform(tool_context, reason: 'Possible spam')
+
+ expect(Current.captain_resolve_reason).to be_nil
+ end
+ end
+
+ describe 'resolving an already resolved conversation' do
+ let(:conversation) { create(:conversation, account: account, inbox: inbox, status: :resolved) }
+
+ it 'does not re-resolve and returns an already resolved message' do
+ queue_adapter = ActiveJob::Base.queue_adapter
+ queue_adapter.enqueued_jobs.clear
+
+ result = tool.perform(tool_context, reason: 'Possible spam')
+
+ expect(result).to include('already resolved')
+ expect(Conversations::ActivityMessageJob).not_to have_been_enqueued
+ end
+ end
+end
From 418bd177f814c089ffedb74251d73abd20099685 Mon Sep 17 00:00:00 2001
From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Date: Fri, 20 Feb 2026 20:20:32 +0530
Subject: [PATCH 011/113] fix: Adjust inbox settings pages layout width
(#13590)
# Pull Request Template
## Description
This PR includes,
1. Adjusting the inbox settings page layout width from 3xl to 4xl for
the collaborators, configuration, and bot configuration sections.
2. Adding a dynamic max-width for inbox settings banners based on the
selected tab.
3. Making the sender name preview layout responsive.
4. Reordering automation rule row buttons so Clone appears before
Delete.
5. Update the Gmail icon ratio.
6. Fix height issues with team/inbox pages
7. The delete button changes to red on hover
8. Add border to conversation header when no dashboard apps present
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---
.../AttributeListItem.vue | 8 ++++-
.../widgets/conversation/ConversationBox.vue | 3 ++
.../dashboard/settings/agentBots/Index.vue | 1 +
.../dashboard/settings/agents/Index.vue | 1 +
.../settings/automation/AutomationRuleRow.vue | 17 +++++-----
.../dashboard/settings/canned/Index.vue | 1 +
.../component/CustomRoleTableBody.vue | 1 +
.../dashboard/settings/inbox/ChannelList.vue | 22 ++++++-------
.../dashboard/settings/inbox/ImapSettings.vue | 8 ++---
.../settings/inbox/InboxChannels.vue | 4 +--
.../routes/dashboard/settings/inbox/Index.vue | 1 +
.../dashboard/settings/inbox/Settings.vue | 32 +++++++++++++++++--
.../dashboard/settings/inbox/SmtpSettings.vue | 14 ++++----
.../inbox/components/BotConfiguration.vue | 2 +-
.../components/SenderNameExamplePreview.vue | 9 +++++-
.../inbox/settingsPage/ConfigurationPage.vue | 14 ++++----
.../DashboardApps/DashboardAppsRow.vue | 1 +
.../integrations/MultipleIntegrationHooks.vue | 1 +
.../integrations/Webhooks/WebhookRow.vue | 1 +
.../dashboard/settings/labels/Index.vue | 1 +
.../settings/macros/MacrosTableRow.vue | 1 +
.../routes/dashboard/settings/sla/Index.vue | 1 +
.../settings/teams/Create/AddAgents.vue | 19 ++++-------
.../settings/teams/Create/CreateTeam.vue | 2 +-
.../dashboard/settings/teams/Create/Index.vue | 2 +-
.../settings/teams/Edit/EditAgents.vue | 19 ++++-------
.../settings/teams/Edit/EditTeam.vue | 2 +-
.../dashboard/settings/teams/Edit/Index.vue | 2 +-
.../routes/dashboard/settings/teams/Index.vue | 1 +
theme/icons.js | 2 +-
30 files changed, 118 insertions(+), 75 deletions(-)
diff --git a/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue b/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue
index 624ba2981..d0b660e27 100644
--- a/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue
+++ b/app/javascript/dashboard/components-next/ConversationWorkflow/AttributeListItem.vue
@@ -82,7 +82,13 @@ const attributeIcon = computed(() => {
sm
@click="emit('edit', attribute)"
/>
-
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue
index 2f08cafc4..7d04470ed 100644
--- a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue
@@ -100,6 +100,9 @@ export default {
v-if="currentChat.id"
:chat="currentChat"
:show-back-button="isOnExpandedLayout && !isInboxView"
+ :class="{
+ 'border-b border-b-n-weak !pt-2': !dashboardApps.length,
+ }"
/>
{
icon="i-woot-bin"
slate
sm
+ class="hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-2"
:is-loading="loading[bot.id]"
@click="openDeletePopup(bot)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue
index 7f1376efe..7c0e3fa3f 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/agents/Index.vue
@@ -268,6 +268,7 @@ const confirmDeletion = () => {
icon="i-woot-bin"
slate
sm
+ class="hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-2"
:is-loading="loading[agent.id]"
@click="openDeletePopup(agent, index)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue
index 3117a51e2..7f573e81a 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleRow.vue
@@ -70,14 +70,6 @@ const automationActive = computed({
:is-loading="loading"
@click="$emit('edit', automation)"
/>
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue
index 9592deb85..1a5c067de 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/canned/Index.vue
@@ -232,6 +232,7 @@ const tableHeaders = computed(() => {
icon="i-woot-bin"
slate
sm
+ class="hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-2"
:is-loading="loading[cannedItem.id]"
@click="openDeletePopup(cannedItem)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
index 1104848d5..c844a01bd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/customRoles/component/CustomRoleTableBody.vue
@@ -66,6 +66,7 @@ const getFormattedPermissions = role => {
icon="i-woot-bin"
slate
sm
+ class="hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-2"
:is-loading="loading[customRole.id]"
@click="emit('delete', customRole)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue
index 0c1494d09..e2ebd27cd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue
@@ -116,17 +116,15 @@ onMounted(() => {
-
-
-
-
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue
index a18a9a0f8..7325793db 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ImapSettings.vue
@@ -115,7 +115,7 @@ export default {
{
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
index 46d8f3cc8..838d3ac0b 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue
@@ -170,6 +170,7 @@ const openDelete = inbox => {
icon="i-woot-bin"
slate
sm
+ class="hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-2"
@click="openDelete(inbox)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
index d8ed89f87..0f7f90bdd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
@@ -214,6 +214,18 @@ export default {
const { medium, channel_type: type } = this.inbox;
return getInboxIconByType(type, medium, 'line');
},
+ bannerMaxWidth() {
+ const narrowTabs = [
+ 'collaborators',
+ 'configuration',
+ 'bot-configuration',
+ ];
+ if (narrowTabs.includes(this.selectedTabKey)) return 'max-w-4xl';
+ if (this.selectedTabKey === 'inbox-settings') {
+ return this.isAWebWidgetInbox ? 'max-w-7xl' : 'max-w-4xl';
+ }
+ return 'max-w-7xl';
+ },
inboxName() {
if (this.isATwilioSMSChannel || this.isATwilioWhatsAppChannel) {
return `${this.inbox.name} (${
@@ -571,44 +583,57 @@ export default {
v-if="microsoftUnauthorized"
:inbox="inbox"
class="mb-4"
+ :class="bannerMaxWidth"
/>
-
+
{{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL') }}
@@ -755,6 +780,7 @@ export default {
@@ -1107,10 +1133,10 @@ export default {
-
+
-
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue
index 70805de69..575e0e5d2 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/SmtpSettings.vue
@@ -175,7 +175,7 @@ export default {
-