feat(widget): Add captain handoff request
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
include Events::Types
|
||||
before_action :render_not_found_if_empty, only: [:toggle_typing, :toggle_status, :set_custom_attributes, :destroy_custom_attributes]
|
||||
before_action :render_not_found_if_empty,
|
||||
only: [:toggle_typing, :toggle_status, :request_handoff, :set_custom_attributes, :destroy_custom_attributes]
|
||||
|
||||
def index
|
||||
@conversation = conversation
|
||||
@@ -64,6 +65,11 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def request_handoff
|
||||
perform_handoff if conversation.pending?
|
||||
head :ok
|
||||
end
|
||||
|
||||
def set_custom_attributes
|
||||
conversation.update!(custom_attributes: permitted_params[:custom_attributes])
|
||||
end
|
||||
@@ -99,4 +105,40 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
message: [:content, :referer_url, :timestamp, :echo_id],
|
||||
custom_attributes: {})
|
||||
end
|
||||
|
||||
def perform_handoff
|
||||
assistant = captain_assistant
|
||||
previous_executed_by = Current.executed_by
|
||||
Current.executed_by = assistant if assistant
|
||||
|
||||
I18n.with_locale(conversation.account.locale) do
|
||||
create_captain_handoff_message(assistant) if assistant
|
||||
conversation.bot_handoff!
|
||||
send_out_of_office_message_after_handoff
|
||||
end
|
||||
ensure
|
||||
Current.executed_by = previous_executed_by
|
||||
end
|
||||
|
||||
def captain_assistant
|
||||
return unless inbox.respond_to?(:captain_assistant)
|
||||
|
||||
inbox.captain_assistant
|
||||
end
|
||||
|
||||
def create_captain_handoff_message(assistant)
|
||||
conversation.messages.create!(
|
||||
message_type: :outgoing,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
sender: assistant,
|
||||
content: assistant.config['handoff_message'].presence || I18n.t('conversations.captain.handoff')
|
||||
)
|
||||
end
|
||||
|
||||
def send_out_of_office_message_after_handoff
|
||||
return if conversation.campaign.present?
|
||||
|
||||
::MessageTemplates::Template::OutOfOffice.perform_if_applicable(conversation)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,6 +62,12 @@ const toggleStatus = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const requestHandoff = async () => {
|
||||
return API.post(
|
||||
`/api/v1/widget/conversations/request_handoff${window.location.search}`
|
||||
);
|
||||
};
|
||||
|
||||
const setCustomAttributes = async customAttributes => {
|
||||
return API.post(
|
||||
`/api/v1/widget/conversations/set_custom_attributes${window.location.search}`,
|
||||
@@ -90,6 +96,7 @@ export {
|
||||
setUserLastSeenAt,
|
||||
sendEmailTranscript,
|
||||
toggleStatus,
|
||||
requestHandoff,
|
||||
setCustomAttributes,
|
||||
deleteCustomAttribute,
|
||||
};
|
||||
|
||||
@@ -5,12 +5,15 @@ import CustomButton from 'shared/components/Button.vue';
|
||||
import FooterReplyTo from 'widget/components/FooterReplyTo.vue';
|
||||
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { CONVERSATION_STATUS } from 'shared/constants/messages';
|
||||
import { sendEmailTranscript } from 'widget/api/conversation';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { IFrameHelper } from '../helpers/utils';
|
||||
import { CHATWOOT_ON_START_CONVERSATION } from '../constants/sdkEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
const CAPTAIN_HANDOFF_REPLY_THRESHOLD = 5;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChatInputWrap,
|
||||
@@ -24,6 +27,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
inReplyTo: null,
|
||||
isRequestingHandoff: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -31,6 +35,7 @@ export default {
|
||||
conversationAttributes: 'conversationAttributes/getConversationParams',
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
conversationSize: 'conversation/getConversationSize',
|
||||
captainReplyCount: 'conversation/getCaptainReplyCount',
|
||||
currentUser: 'contacts/getCurrentUser',
|
||||
isWidgetStyleFlat: 'appConfig/isWidgetStyleFlat',
|
||||
}),
|
||||
@@ -45,6 +50,12 @@ export default {
|
||||
showEmailTranscriptButton() {
|
||||
return this.hasEmail;
|
||||
},
|
||||
showCaptainHandoffButton() {
|
||||
return (
|
||||
this.conversationAttributes.status === CONVERSATION_STATUS.PENDING &&
|
||||
this.captainReplyCount >= CAPTAIN_HANDOFF_REPLY_THRESHOLD
|
||||
);
|
||||
},
|
||||
hasEmail() {
|
||||
return this.currentUser && this.currentUser.has_email;
|
||||
},
|
||||
@@ -58,7 +69,11 @@ export default {
|
||||
emitter.on(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.toggleReplyTo);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('conversation', ['sendMessage', 'sendAttachment']),
|
||||
...mapActions('conversation', [
|
||||
'sendMessage',
|
||||
'sendAttachment',
|
||||
'requestHandoff',
|
||||
]),
|
||||
...mapActions('conversationAttributes', ['getAttributes']),
|
||||
async handleSendMessage(content) {
|
||||
await this.sendMessage({
|
||||
@@ -90,6 +105,18 @@ export default {
|
||||
toggleReplyTo(message) {
|
||||
this.inReplyTo = message;
|
||||
},
|
||||
async handleRequestHandoff() {
|
||||
try {
|
||||
this.isRequestingHandoff = true;
|
||||
await this.requestHandoff();
|
||||
} catch (error) {
|
||||
emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
message: this.$t('CAPTAIN_HANDOFF.ERROR'),
|
||||
});
|
||||
} finally {
|
||||
this.isRequestingHandoff = false;
|
||||
}
|
||||
},
|
||||
async sendTranscript() {
|
||||
if (this.hasEmail) {
|
||||
try {
|
||||
@@ -124,6 +151,17 @@ export default {
|
||||
:in-reply-to="inReplyTo"
|
||||
@dismiss="inReplyTo = null"
|
||||
/>
|
||||
<CustomButton
|
||||
v-if="showCaptainHandoffButton"
|
||||
block
|
||||
class="mb-2 font-medium"
|
||||
:bg-color="widgetColor"
|
||||
:text-color="textColor"
|
||||
:disabled="isRequestingHandoff"
|
||||
@click="handleRequestHandoff"
|
||||
>
|
||||
{{ $t('CAPTAIN_HANDOFF.BUTTON_TEXT') }}
|
||||
</CustomButton>
|
||||
<ChatInputWrap
|
||||
class="shadow-sm"
|
||||
:on-send-message="handleSendMessage"
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
"CONTINUE_CONVERSATION": "Continue conversation",
|
||||
"YOU": "You",
|
||||
"START_NEW_CONVERSATION": "Start a new conversation",
|
||||
"CAPTAIN_HANDOFF": {
|
||||
"BUTTON_TEXT": "Talk to a support agent",
|
||||
"ERROR": "Could not request a handoff, please try again"
|
||||
},
|
||||
"VIEW_UNREAD_MESSAGES": "You have unread messages",
|
||||
"UNREAD_VIEW": {
|
||||
"VIEW_MESSAGES_BUTTON": "See new messages",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
toggleTyping,
|
||||
setUserLastSeenAt,
|
||||
toggleStatus,
|
||||
requestHandoff,
|
||||
setCustomAttributes,
|
||||
deleteCustomAttribute,
|
||||
} from 'widget/api/conversation';
|
||||
@@ -212,6 +213,11 @@ export const actions = {
|
||||
await toggleStatus();
|
||||
},
|
||||
|
||||
requestHandoff: async ({ dispatch }) => {
|
||||
await requestHandoff();
|
||||
dispatch('conversationAttributes/getAttributes', {}, { root: true });
|
||||
},
|
||||
|
||||
setCustomAttributes: async (
|
||||
{ commit, rootGetters },
|
||||
customAttributes = {}
|
||||
|
||||
@@ -39,6 +39,15 @@ export const getters = {
|
||||
getMessageCount: _state => {
|
||||
return Object.values(_state.conversations).length;
|
||||
},
|
||||
getCaptainReplyCount: _state => {
|
||||
return Object.values(_state.conversations).filter(message => {
|
||||
const { message_type: messageType, sender = {} } = message;
|
||||
return (
|
||||
messageType === MESSAGE_TYPE.OUTGOING &&
|
||||
sender.type === 'captain_assistant'
|
||||
);
|
||||
}).length;
|
||||
},
|
||||
getUnreadMessageCount: _state => {
|
||||
const { userLastSeenAt } = _state.meta;
|
||||
return Object.values(_state.conversations).filter(chat => {
|
||||
|
||||
@@ -419,6 +419,7 @@ Rails.application.routes.draw do
|
||||
post :update_last_seen
|
||||
post :toggle_typing
|
||||
post :transcript
|
||||
post :request_handoff
|
||||
get :toggle_status
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user