diff --git a/.env.example b/.env.example index de671599c..3ff349ffc 100644 --- a/.env.example +++ b/.env.example @@ -256,6 +256,8 @@ AZURE_APP_SECRET= ## Change these values to fine tune performance # control the concurrency setting of sidekiq # SIDEKIQ_CONCURRENCY=10 +# Enable verbose logging each time a job is dequeued in Sidekiq +# ENABLE_SIDEKIQ_DEQUEUE_LOGGER=false # AI powered features diff --git a/app/controllers/api/v1/accounts/bulk_actions_controller.rb b/app/controllers/api/v1/accounts/bulk_actions_controller.rb index 1b8babbc9..222c66714 100644 --- a/app/controllers/api/v1/accounts/bulk_actions_controller.rb +++ b/app/controllers/api/v1/accounts/bulk_actions_controller.rb @@ -5,6 +5,7 @@ class Api::V1::Accounts::BulkActionsController < Api::V1::Accounts::BaseControll enqueue_conversation_job head :ok when 'Contact' + check_authorization_for_contact_action enqueue_contact_job head :ok else @@ -34,14 +35,34 @@ class Api::V1::Accounts::BulkActionsController < Api::V1::Accounts::BaseControll ) end + def delete_contact_action? + params[:action_name] == 'delete' + end + + def check_authorization_for_contact_action + authorize(Contact, :destroy?) if delete_contact_action? + end + def conversation_params - params.permit(:type, :snoozed_until, ids: [], fields: [:status, :assignee_id, :team_id], labels: [add: [], remove: []]) + # TODO: Align conversation payloads with the `{ action_name, action_attributes }` + # and then remove this method in favor of a common params method. + base = params.permit( + :snoozed_until, + fields: [:status, :assignee_id, :team_id] + ) + append_common_bulk_attributes(base) end def contact_params - params.require(:ids) - permitted = params.permit(:type, ids: [], labels: [add: []]) - permitted[:ids] = permitted[:ids].map(&:to_i) if permitted[:ids].present? - permitted + # TODO: remove this method in favor of a common params method. + # once legacy conversation payloads are migrated. + append_common_bulk_attributes({}) + end + + def append_common_bulk_attributes(base_params) + # NOTE: Conversation payloads historically diverged per action. Going forward we + # want all objects to share a common contract: `{ action_name, action_attributes }` + common = params.permit(:type, :action_name, ids: [], labels: [add: [], remove: []]) + base_params.merge(common) end end diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue index 47b779b61..041f06410 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue @@ -5,6 +5,7 @@ import { useToggle } from '@vueuse/core'; import Button from 'dashboard/components-next/button/Button.vue'; import ConfirmContactDeleteDialog from 'dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue'; +import Policy from 'dashboard/components/policy.vue'; defineProps({ selectedContact: { @@ -24,42 +25,44 @@ const openConfirmDeleteContactDialog = () => { diff --git a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue index aec21a976..b7014c42f 100644 --- a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue +++ b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue @@ -10,6 +10,7 @@ import Button from 'dashboard/components-next/button/Button.vue'; import ContactLabels from 'dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue'; import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/ContactsForm.vue'; import ConfirmContactDeleteDialog from 'dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue'; +import Policy from 'dashboard/components/policy.vue'; const props = defineProps({ selectedContact: { @@ -174,27 +175,29 @@ const handleAvatarDelete = async () => { @click="updateContact" /> -
-
-
- {{ t('CONTACTS_LAYOUT.DETAILS.DELETE_CONTACT') }} -
- - {{ t('CONTACTS_LAYOUT.DETAILS.DELETE_CONTACT_DESCRIPTION') }} - + +
+
+
+ {{ t('CONTACTS_LAYOUT.DETAILS.DELETE_CONTACT') }} +
+ + {{ t('CONTACTS_LAYOUT.DETAILS.DELETE_CONTACT_DESCRIPTION') }} + +
+
-
- +
diff --git a/app/javascript/dashboard/components-next/Conversation/SidepanelSwitch.vue b/app/javascript/dashboard/components-next/Conversation/SidepanelSwitch.vue index 1ee969275..5e96df3c6 100644 --- a/app/javascript/dashboard/components-next/Conversation/SidepanelSwitch.vue +++ b/app/javascript/dashboard/components-next/Conversation/SidepanelSwitch.vue @@ -1,5 +1,6 @@ diff --git a/app/javascript/dashboard/components-next/Editor/Editor.vue b/app/javascript/dashboard/components-next/Editor/Editor.vue index a2f139bdc..67936fa59 100644 --- a/app/javascript/dashboard/components-next/Editor/Editor.vue +++ b/app/javascript/dashboard/components-next/Editor/Editor.vue @@ -21,6 +21,10 @@ const props = defineProps({ enableCannedResponses: { type: Boolean, default: true }, enabledMenuOptions: { type: Array, default: () => [] }, enableCaptainTools: { type: Boolean, default: false }, + signature: { type: String, default: '' }, + allowSignature: { type: Boolean, default: false }, + sendWithSignature: { type: Boolean, default: false }, + channelType: { type: String, default: '' }, }); const emit = defineEmits(['update:modelValue']); @@ -100,6 +104,10 @@ watch( :enable-canned-responses="enableCannedResponses" :enabled-menu-options="enabledMenuOptions" :enable-captain-tools="enableCaptainTools" + :signature="signature" + :allow-signature="allowSignature" + :send-with-signature="sendWithSignature" + :channel-type="channelType" @input="handleInput" @focus="handleFocus" @blur="handleBlur" diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticleEditorPage/ArticleEditorHeader.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticleEditorPage/ArticleEditorHeader.vue index 2d8b219f7..bec54593f 100644 --- a/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticleEditorPage/ArticleEditorHeader.vue +++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticleEditorPage/ArticleEditorHeader.vue @@ -14,6 +14,7 @@ import { } from 'dashboard/helper/portalHelper'; import wootConstants from 'dashboard/constants/globals'; +import ButtonGroup from 'dashboard/components-next/buttonGroup/ButtonGroup.vue'; import Button from 'dashboard/components-next/button/Button.vue'; import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue'; @@ -140,11 +141,12 @@ const updateArticleStatus = async ({ value }) => { :disabled="!articleId" @click="previewArticle" /> -
+
- + diff --git a/app/javascript/dashboard/components-next/NewConversation/components/ActionButtons.vue b/app/javascript/dashboard/components-next/NewConversation/components/ActionButtons.vue index 773ebe315..92c5850de 100644 --- a/app/javascript/dashboard/components-next/NewConversation/components/ActionButtons.vue +++ b/app/javascript/dashboard/components-next/NewConversation/components/ActionButtons.vue @@ -92,7 +92,6 @@ const setSignature = () => { const toggleMessageSignature = () => { setSignatureFlagForInbox(props.channelType, !sendWithSignature.value); - setSignature(); }; // Added this watch to dynamically set signature on target inbox change. diff --git a/app/javascript/dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue b/app/javascript/dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue index 4d6d41dac..a02d6d495 100644 --- a/app/javascript/dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue +++ b/app/javascript/dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue @@ -199,16 +199,20 @@ const handleInboxAction = ({ value, action, ...rest }) => { state.attachedFiles = []; }; -const removeTargetInbox = value => { - v$.value.$reset(); - // Remove the signature from message content - // Based on the Advance Editor (used in isEmailOrWebWidget) and Plain editor(all other inboxes except WhatsApp) - if (props.sendWithSignature) { - const signatureToRemove = inboxTypes.value.isEmailOrWebWidget - ? props.messageSignature - : extractTextFromMarkdown(props.messageSignature); +const removeSignatureFromMessage = () => { + // Always remove the signature from message content when inbox/contact is removed + // to ensure no leftover signature content remains + const signatureToRemove = inboxTypes.value.isEmailOrWebWidget + ? props.messageSignature + : extractTextFromMarkdown(props.messageSignature); + if (signatureToRemove) { state.message = removeSignature(state.message, signatureToRemove); } +}; + +const removeTargetInbox = value => { + v$.value.$reset(); + removeSignatureFromMessage(); emit('updateTargetInbox', value); state.attachedFiles = []; }; @@ -216,6 +220,7 @@ const removeTargetInbox = value => { const clearSelectedContact = () => { emit('clearSelectedContact'); state.attachedFiles = []; + removeSignatureFromMessage(); }; const onClickInsertEmoji = emoji => { @@ -354,6 +359,7 @@ const shouldShowMessageEditor = computed(() => { :is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget" :has-errors="validationStates.isMessageInvalid" :has-attachments="state.attachedFiles.length > 0" + :channel-type="inboxChannelType" /> { " enable-variables :show-character-count="false" + :signature="messageSignature" + allow-signature + :send-with-signature="sendWithSignature" + :channel-type="channelType" />