feat(widget): Add captain handoff request

This commit is contained in:
aakashb95
2026-05-01 23:01:05 +05:30
parent 68cd725a62
commit e89d8567fd
7 changed files with 109 additions and 2 deletions
@@ -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 => {