feat: Add ability to create a new conversation if the previous conversation is resolved (#2512)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
e6e9916fdb
commit
f0f66c7da6
13 changed files with 119 additions and 16 deletions
|
@ -2,4 +2,5 @@ export const BUS_EVENTS = {
|
||||||
SET_REFERRER_HOST: 'SET_REFERRER_HOST',
|
SET_REFERRER_HOST: 'SET_REFERRER_HOST',
|
||||||
SET_TWEET_REPLY: 'SET_TWEET_REPLY',
|
SET_TWEET_REPLY: 'SET_TWEET_REPLY',
|
||||||
ATTACHMENT_SIZE_CHECK_ERROR: 'ATTACHMENT_SIZE_CHECK_ERROR',
|
ATTACHMENT_SIZE_CHECK_ERROR: 'ATTACHMENT_SIZE_CHECK_ERROR',
|
||||||
|
START_NEW_CONVERSATION: 'START_NEW_CONVERSATION',
|
||||||
};
|
};
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
this.sendRNWebViewLoadedEvent();
|
this.sendRNWebViewLoadedEvent();
|
||||||
}
|
}
|
||||||
this.$store.dispatch('conversationAttributes/get');
|
this.$store.dispatch('conversationAttributes/getAttributes');
|
||||||
this.setWidgetColor(window.chatwootWebChannel);
|
this.setWidgetColor(window.chatwootWebChannel);
|
||||||
this.registerUnreadEvents();
|
this.registerUnreadEvents();
|
||||||
this.registerCampaignEvents();
|
this.registerCampaignEvents();
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<footer class="footer">
|
<footer v-if="!hideReplyBox" class="footer">
|
||||||
<ChatInputWrap
|
<ChatInputWrap
|
||||||
:on-send-message="handleSendMessage"
|
:on-send-message="handleSendMessage"
|
||||||
:on-send-attachment="handleSendAttachment"
|
:on-send-attachment="handleSendAttachment"
|
||||||
/>
|
/>
|
||||||
</footer>
|
</footer>
|
||||||
|
<custom-button
|
||||||
|
v-else
|
||||||
|
class="font-medium"
|
||||||
|
block
|
||||||
|
:bg-color="widgetColor"
|
||||||
|
:text-color="textColor"
|
||||||
|
@click="startNewConversation"
|
||||||
|
>
|
||||||
|
{{ $t('START_NEW_CONVERSATION') }}
|
||||||
|
</custom-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||||
|
import CustomButton from 'shared/components/Button';
|
||||||
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
|
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
|
||||||
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ChatInputWrap,
|
ChatInputWrap,
|
||||||
|
CustomButton,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
msg: {
|
msg: {
|
||||||
|
@ -21,16 +35,49 @@ export default {
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
conversationAttributes: 'conversationAttributes/getConversationParams',
|
||||||
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
|
getConversationSize: 'conversation/getConversationSize',
|
||||||
|
}),
|
||||||
|
textColor() {
|
||||||
|
return getContrastingTextColor(this.widgetColor);
|
||||||
|
},
|
||||||
|
hideReplyBox() {
|
||||||
|
const { csatSurveyEnabled } = window.chatwootWebChannel;
|
||||||
|
const { status } = this.conversationAttributes;
|
||||||
|
return csatSurveyEnabled && status === 'resolved';
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('conversation', ['sendMessage', 'sendAttachment']),
|
...mapActions('conversation', [
|
||||||
handleSendMessage(content) {
|
'sendMessage',
|
||||||
this.sendMessage({
|
'sendAttachment',
|
||||||
|
'clearConversations',
|
||||||
|
]),
|
||||||
|
...mapActions('conversationAttributes', [
|
||||||
|
'getAttributes',
|
||||||
|
'clearConversationAttributes',
|
||||||
|
]),
|
||||||
|
async handleSendMessage(content) {
|
||||||
|
const conversationSize = this.getConversationSize;
|
||||||
|
await this.sendMessage({
|
||||||
content,
|
content,
|
||||||
});
|
});
|
||||||
|
// Update conversation attributes on new conversation
|
||||||
|
if (conversationSize === 0) {
|
||||||
|
this.getAttributes();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleSendAttachment(attachment) {
|
handleSendAttachment(attachment) {
|
||||||
this.sendAttachment({ attachment });
|
this.sendAttachment({ attachment });
|
||||||
},
|
},
|
||||||
|
startNewConversation() {
|
||||||
|
this.clearConversations();
|
||||||
|
this.clearConversationAttributes();
|
||||||
|
window.bus.$emit(BUS_EVENTS.START_NEW_CONVERSATION);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"IN_A_DAY": "Typically replies in a day"
|
"IN_A_DAY": "Typically replies in a day"
|
||||||
},
|
},
|
||||||
"START_CONVERSATION": "Start Conversation",
|
"START_CONVERSATION": "Start Conversation",
|
||||||
|
"START_NEW_CONVERSATION": "Start a new conversation",
|
||||||
"UNREAD_VIEW": {
|
"UNREAD_VIEW": {
|
||||||
"VIEW_MESSAGES_BUTTON": "See new messages",
|
"VIEW_MESSAGES_BUTTON": "See new messages",
|
||||||
"CLOSE_MESSAGES_BUTTON": "Close",
|
"CLOSE_MESSAGES_BUTTON": "Close",
|
||||||
|
|
|
@ -22,10 +22,16 @@ export default {
|
||||||
return window.chatwootWebChannel.preChatFormEnabled;
|
return window.chatwootWebChannel.preChatFormEnabled;
|
||||||
},
|
},
|
||||||
preChatFormOptions() {
|
preChatFormOptions() {
|
||||||
|
let requireEmail = false;
|
||||||
|
let preChatMessage = '';
|
||||||
const options = window.chatwootWebChannel.preChatFormOptions || {};
|
const options = window.chatwootWebChannel.preChatFormOptions || {};
|
||||||
|
if (!this.isOnNewConversation) {
|
||||||
|
requireEmail = options.require_email;
|
||||||
|
preChatMessage = options.pre_chat_message;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
requireEmail: options.require_email,
|
requireEmail,
|
||||||
preChatMessage: options.pre_chat_message,
|
preChatMessage,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@ global.chatwootWebChannel = {
|
||||||
avatarUrl: 'https://test.url',
|
avatarUrl: 'https://test.url',
|
||||||
hasAConnectedAgentBot: 'AgentBot',
|
hasAConnectedAgentBot: 'AgentBot',
|
||||||
enabledFeatures: ['emoji_picker', 'attachments'],
|
enabledFeatures: ['emoji_picker', 'attachments'],
|
||||||
|
preChatFormOptions: { require_email: false, pre_chat_message: '' },
|
||||||
};
|
};
|
||||||
|
|
||||||
global.chatwootWidgetDefaults = {
|
global.chatwootWidgetDefaults = {
|
||||||
|
@ -31,6 +32,14 @@ describe('configMixin', () => {
|
||||||
avatarUrl: 'https://test.url',
|
avatarUrl: 'https://test.url',
|
||||||
hasAConnectedAgentBot: 'AgentBot',
|
hasAConnectedAgentBot: 'AgentBot',
|
||||||
enabledFeatures: ['emoji_picker', 'attachments'],
|
enabledFeatures: ['emoji_picker', 'attachments'],
|
||||||
|
preChatFormOptions: {
|
||||||
|
pre_chat_message: '',
|
||||||
|
require_email: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.preChatFormOptions).toEqual({
|
||||||
|
requireEmail: false,
|
||||||
|
preChatMessage: '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { refreshActionCableConnector } from '../../../helpers/actionCable';
|
||||||
import { createTemporaryMessage, onNewMessageCreated } from './helpers';
|
import { createTemporaryMessage, onNewMessageCreated } from './helpers';
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
createConversation: async ({ commit }, params) => {
|
createConversation: async ({ commit, dispatch }, params) => {
|
||||||
commit('setConversationUIFlag', { isCreating: true });
|
commit('setConversationUIFlag', { isCreating: true });
|
||||||
try {
|
try {
|
||||||
const { data } = await createConversationAPI(params);
|
const { data } = await createConversationAPI(params);
|
||||||
|
@ -22,6 +22,7 @@ export const actions = {
|
||||||
const [message = {}] = messages;
|
const [message = {}] = messages;
|
||||||
commit('pushMessageToConversation', message);
|
commit('pushMessageToConversation', message);
|
||||||
refreshActionCableConnector(pubsubToken);
|
refreshActionCableConnector(pubsubToken);
|
||||||
|
dispatch('conversationAttributes/getAttributes', {}, { root: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
// Ignore error
|
// Ignore error
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
SET_CONVERSATION_ATTRIBUTES,
|
SET_CONVERSATION_ATTRIBUTES,
|
||||||
UPDATE_CONVERSATION_ATTRIBUTES,
|
UPDATE_CONVERSATION_ATTRIBUTES,
|
||||||
|
CLEAR_CONVERSATION_ATTRIBUTES,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { getConversationAPI } from '../../api/conversation';
|
import { getConversationAPI } from '../../api/conversation';
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ export const getters = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
get: async ({ commit }) => {
|
getAttributes: async ({ commit }) => {
|
||||||
try {
|
try {
|
||||||
const { data } = await getConversationAPI();
|
const { data } = await getConversationAPI();
|
||||||
const { contact_last_seen_at: lastSeen } = data;
|
const { contact_last_seen_at: lastSeen } = data;
|
||||||
|
@ -27,6 +28,9 @@ export const actions = {
|
||||||
update({ commit }, data) {
|
update({ commit }, data) {
|
||||||
commit(UPDATE_CONVERSATION_ATTRIBUTES, data);
|
commit(UPDATE_CONVERSATION_ATTRIBUTES, data);
|
||||||
},
|
},
|
||||||
|
clearConversationAttributes: ({ commit }) => {
|
||||||
|
commit('CLEAR_CONVERSATION_ATTRIBUTES');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
|
@ -40,6 +44,10 @@ export const mutations = {
|
||||||
$state.status = data.status;
|
$state.status = data.status;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[CLEAR_CONVERSATION_ATTRIBUTES]($state) {
|
||||||
|
$state.id = '';
|
||||||
|
$state.status = '';
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -5,10 +5,10 @@ const commit = jest.fn();
|
||||||
jest.mock('widget/helpers/axios');
|
jest.mock('widget/helpers/axios');
|
||||||
|
|
||||||
describe('#actions', () => {
|
describe('#actions', () => {
|
||||||
describe('#update', () => {
|
describe('#get attributes', () => {
|
||||||
it('sends mutation if api is success', async () => {
|
it('sends mutation if api is success', async () => {
|
||||||
API.get.mockResolvedValue({ data: { id: 1, status: 'bot' } });
|
API.get.mockResolvedValue({ data: { id: 1, status: 'bot' } });
|
||||||
await actions.get({ commit });
|
await actions.getAttributes({ commit });
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
['SET_CONVERSATION_ATTRIBUTES', { id: 1, status: 'bot' }],
|
['SET_CONVERSATION_ATTRIBUTES', { id: 1, status: 'bot' }],
|
||||||
['conversation/setMetaUserLastSeenAt', undefined, { root: true }],
|
['conversation/setMetaUserLastSeenAt', undefined, { root: true }],
|
||||||
|
@ -16,12 +16,12 @@ describe('#actions', () => {
|
||||||
});
|
});
|
||||||
it('doesnot send mutation if api is error', async () => {
|
it('doesnot send mutation if api is error', async () => {
|
||||||
API.get.mockRejectedValue({ message: 'Invalid Headers' });
|
API.get.mockRejectedValue({ message: 'Invalid Headers' });
|
||||||
await actions.get({ commit });
|
await actions.getAttributes({ commit });
|
||||||
expect(commit.mock.calls).toEqual([]);
|
expect(commit.mock.calls).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#update', () => {
|
describe('#update attributes', () => {
|
||||||
it('sends correct mutations', () => {
|
it('sends correct mutations', () => {
|
||||||
actions.update({ commit }, { id: 1, status: 'bot' });
|
actions.update({ commit }, { id: 1, status: 'bot' });
|
||||||
expect(commit).toBeCalledWith('UPDATE_CONVERSATION_ATTRIBUTES', {
|
expect(commit).toBeCalledWith('UPDATE_CONVERSATION_ATTRIBUTES', {
|
||||||
|
@ -30,4 +30,10 @@ describe('#actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#clear attributes', () => {
|
||||||
|
it('sends correct mutations', () => {
|
||||||
|
actions.clearConversationAttributes({ commit });
|
||||||
|
expect(commit).toBeCalledWith('CLEAR_CONVERSATION_ATTRIBUTES');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,4 +30,15 @@ describe('#mutations', () => {
|
||||||
expect(state).toEqual({ id: 1, status: 'bot' });
|
expect(state).toEqual({ id: 1, status: 'bot' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#CLEAR_CONVERSATION_ATTRIBUTES', () => {
|
||||||
|
it('clear status if it is same conversation', () => {
|
||||||
|
const state = { id: 1, status: 'open' };
|
||||||
|
mutations.CLEAR_CONVERSATION_ATTRIBUTES(state, {
|
||||||
|
id: 1,
|
||||||
|
status: 'open',
|
||||||
|
});
|
||||||
|
expect(state).toEqual({ id: '', status: '' });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR';
|
export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR';
|
||||||
export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES';
|
export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES';
|
||||||
export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES';
|
export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES';
|
||||||
|
export const CLEAR_CONVERSATION_ATTRIBUTES = 'CLEAR_CONVERSATION_ATTRIBUTES';
|
||||||
|
|
|
@ -113,7 +113,11 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { isOnCollapsedView: false, showAttachmentError: false };
|
return {
|
||||||
|
isOnCollapsedView: false,
|
||||||
|
showAttachmentError: false,
|
||||||
|
isOnNewConversation: false,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
@ -130,7 +134,10 @@ export default {
|
||||||
if (this.conversationSize) {
|
if (this.conversationSize) {
|
||||||
return 'messageView';
|
return 'messageView';
|
||||||
}
|
}
|
||||||
if (this.preChatFormEnabled && !currentUserEmail) {
|
if (
|
||||||
|
this.isOnNewConversation ||
|
||||||
|
(this.preChatFormEnabled && !currentUserEmail)
|
||||||
|
) {
|
||||||
return 'preChatFormView';
|
return 'preChatFormView';
|
||||||
}
|
}
|
||||||
return 'messageView';
|
return 'messageView';
|
||||||
|
@ -163,6 +170,10 @@ export default {
|
||||||
this.showAttachmentError = false;
|
this.showAttachmentError = false;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
|
bus.$on(BUS_EVENTS.START_NEW_CONVERSATION, () => {
|
||||||
|
this.isOnCollapsedView = true;
|
||||||
|
this.isOnNewConversation = true;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
startConversation() {
|
startConversation() {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
preChatFormEnabled: <%= @web_widget.pre_chat_form_enabled %>,
|
preChatFormEnabled: <%= @web_widget.pre_chat_form_enabled %>,
|
||||||
preChatFormOptions: <%= @web_widget.pre_chat_form_options.to_json.html_safe %>,
|
preChatFormOptions: <%= @web_widget.pre_chat_form_options.to_json.html_safe %>,
|
||||||
workingHoursEnabled: <%= @web_widget.inbox.working_hours_enabled %>,
|
workingHoursEnabled: <%= @web_widget.inbox.working_hours_enabled %>,
|
||||||
|
csatSurveyEnabled: <%= @web_widget.inbox.csat_survey_enabled %>,
|
||||||
workingHours: <%= @web_widget.inbox.working_hours.to_json.html_safe %>,
|
workingHours: <%= @web_widget.inbox.working_hours.to_json.html_safe %>,
|
||||||
outOfOfficeMessage: <%= @web_widget.inbox.out_of_office_message.to_json.html_safe %>,
|
outOfOfficeMessage: <%= @web_widget.inbox.out_of_office_message.to_json.html_safe %>,
|
||||||
utcOffset: '<%= ActiveSupport::TimeZone[@web_widget.inbox.timezone].formatted_offset %>'
|
utcOffset: '<%= ActiveSupport::TimeZone[@web_widget.inbox.timezone].formatted_offset %>'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue