feat: Show pre-chat form before triggering the campaign (#3215)
This commit is contained in:
parent
76370267f3
commit
c6326993df
9 changed files with 175 additions and 26 deletions
|
@ -8,6 +8,7 @@
|
|||
:is-left-aligned="isLeftAligned"
|
||||
:hide-message-bubble="hideMessageBubble"
|
||||
:show-popout-button="showPopoutButton"
|
||||
:is-campaign-view-clicked="isCampaignViewClicked"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
@ -15,18 +16,19 @@
|
|||
import { mapGetters, mapActions, mapMutations } from 'vuex';
|
||||
import { setHeader } from 'widget/helpers/axios';
|
||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||
import configMixin from './mixins/configMixin';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import Router from './views/Router';
|
||||
import { getLocale } from './helpers/urlParamsHelper';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Router,
|
||||
},
|
||||
mixins: [availabilityMixin],
|
||||
mixins: [availabilityMixin, configMixin],
|
||||
data() {
|
||||
return {
|
||||
showUnreadView: false,
|
||||
|
@ -36,6 +38,7 @@ export default {
|
|||
widgetPosition: 'right',
|
||||
showPopoutButton: false,
|
||||
isWebWidgetTriggered: false,
|
||||
isCampaignViewClicked: false,
|
||||
isWidgetOpen: false,
|
||||
};
|
||||
},
|
||||
|
@ -98,7 +101,11 @@ export default {
|
|||
methods: {
|
||||
...mapActions('appConfig', ['setWidgetColor']),
|
||||
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
|
||||
...mapActions('campaign', ['initCampaigns', 'executeCampaign']),
|
||||
...mapActions('campaign', [
|
||||
'initCampaigns',
|
||||
'executeCampaign',
|
||||
'resetCampaign',
|
||||
]),
|
||||
...mapActions('agent', ['fetchAvailableAgents']),
|
||||
...mapMutations('events', ['toggleOpen']),
|
||||
scrollConversationToBottom() {
|
||||
|
@ -147,15 +154,25 @@ export default {
|
|||
});
|
||||
},
|
||||
registerCampaignEvents() {
|
||||
bus.$on('on-campaign-view-clicked', campaignId => {
|
||||
const { websiteToken } = window.chatwootWebChannel;
|
||||
bus.$on('on-campaign-view-clicked', () => {
|
||||
this.isCampaignViewClicked = true;
|
||||
this.showCampaignView = false;
|
||||
this.showUnreadView = false;
|
||||
this.unsetUnreadView();
|
||||
this.setUserLastSeen();
|
||||
// Execute campaign only if pre-chat form (and require email too) is not enabled
|
||||
if (
|
||||
!(this.preChatFormEnabled && this.preChatFormOptions.requireEmail)
|
||||
) {
|
||||
bus.$emit('execute-campaign', this.activeCampaign.id);
|
||||
}
|
||||
});
|
||||
bus.$on('execute-campaign', campaignId => {
|
||||
const { websiteToken } = window.chatwootWebChannel;
|
||||
this.executeCampaign({ campaignId, websiteToken });
|
||||
});
|
||||
},
|
||||
|
||||
setPopoutDisplay(showPopoutButton) {
|
||||
this.showPopoutButton = showPopoutButton;
|
||||
},
|
||||
|
@ -255,6 +272,10 @@ export default {
|
|||
this.showUnreadView = true;
|
||||
this.showCampaignView = false;
|
||||
} else if (message.event === 'unset-unread-view') {
|
||||
// Reset campaign, If widget opened via clciking on bubble button
|
||||
if (!this.isCampaignViewClicked) {
|
||||
this.resetCampaign();
|
||||
}
|
||||
this.showUnreadView = false;
|
||||
this.showCampaignView = false;
|
||||
} else if (message.event === 'toggle-open') {
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
class="flex flex-1 flex-col p-6 overflow-y-auto"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<div v-if="options.preChatMessage" class="text-black-800 text-sm leading-5">
|
||||
{{ options.preChatMessage }}
|
||||
<div
|
||||
v-if="shouldShowHeaderMessage"
|
||||
class="text-black-800 text-sm leading-5"
|
||||
>
|
||||
{{ headerMessage }}
|
||||
</div>
|
||||
<form-input
|
||||
v-if="options.requireEmail"
|
||||
|
@ -31,6 +34,7 @@
|
|||
"
|
||||
/>
|
||||
<form-text-area
|
||||
v-if="!activeCampaignExist"
|
||||
v-model="message"
|
||||
class="my-5"
|
||||
:label="$t('PRE_CHAT_FORM.FIELDS.MESSAGE.LABEL')"
|
||||
|
@ -38,7 +42,7 @@
|
|||
:error="$v.message.$error ? $t('PRE_CHAT_FORM.FIELDS.MESSAGE.ERROR') : ''"
|
||||
/>
|
||||
<custom-button
|
||||
class="font-medium"
|
||||
class="font-medium my-5"
|
||||
block
|
||||
:bg-color="widgetColor"
|
||||
:text-color="textColor"
|
||||
|
@ -58,6 +62,8 @@ import Spinner from 'shared/components/Spinner';
|
|||
import { mapGetters } from 'vuex';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
import { required, minLength, email } from 'vuelidate/lib/validators';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormInput,
|
||||
|
@ -88,6 +94,10 @@ export default {
|
|||
minLength: minLength(1),
|
||||
},
|
||||
};
|
||||
// For campaign, message field is not required
|
||||
if (this.activeCampaignExist) {
|
||||
return identityValidations;
|
||||
}
|
||||
if (this.options.requireEmail) {
|
||||
return {
|
||||
...identityValidations,
|
||||
|
@ -107,10 +117,23 @@ export default {
|
|||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
isCreating: 'conversation/getIsCreating',
|
||||
activeCampaign: 'campaign/getActiveCampaign',
|
||||
}),
|
||||
textColor() {
|
||||
return getContrastingTextColor(this.widgetColor);
|
||||
},
|
||||
activeCampaignExist() {
|
||||
return !isEmptyObject(this.activeCampaign);
|
||||
},
|
||||
shouldShowHeaderMessage() {
|
||||
return this.activeCampaignExist || this.options.preChatMessage;
|
||||
},
|
||||
headerMessage() {
|
||||
if (this.activeCampaignExist) {
|
||||
return this.$t('PRE_CHAT_FORM.CAMPAIGN_HEADER');
|
||||
}
|
||||
return this.options.preChatMessage;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
|
@ -118,11 +141,22 @@ export default {
|
|||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('conversation/createConversation', {
|
||||
fullName: this.fullName,
|
||||
emailAddress: this.emailAddress,
|
||||
message: this.message,
|
||||
});
|
||||
// Check any active campaign exist or not
|
||||
if (this.activeCampaignExist) {
|
||||
bus.$emit('execute-campaign', this.activeCampaign.id);
|
||||
this.$store.dispatch('contacts/update', {
|
||||
user: {
|
||||
email: this.emailAddress,
|
||||
name: this.fullName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.$store.dispatch('conversation/createConversation', {
|
||||
fullName: this.fullName,
|
||||
emailAddress: this.emailAddress,
|
||||
message: this.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -50,7 +50,8 @@
|
|||
"PLACEHOLDER": "Please enter your message",
|
||||
"ERROR": "Message too short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CAMPAIGN_HEADER": "Please provide your name and email before starting the conversation"
|
||||
},
|
||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
||||
"CHAT_FORM": {
|
||||
|
|
|
@ -12,6 +12,7 @@ const state = {
|
|||
hasFetched: false,
|
||||
},
|
||||
activeCampaign: {},
|
||||
campaignHasExecuted: false,
|
||||
};
|
||||
|
||||
const resetCampaignTimers = (
|
||||
|
@ -34,6 +35,7 @@ export const getters = {
|
|||
getHasFetched: $state => $state.uiFlags.hasFetched,
|
||||
getCampaigns: $state => $state.records,
|
||||
getActiveCampaign: $state => $state.activeCampaign,
|
||||
getCampaignHasExecuted: $state => $state.campaignHasExecuted,
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
|
@ -76,17 +78,37 @@ export const actions = {
|
|||
);
|
||||
}
|
||||
},
|
||||
startCampaign: async ({ commit }, { websiteToken, campaignId }) => {
|
||||
const { data: campaigns } = await getCampaigns(websiteToken);
|
||||
const campaign = campaigns.find(item => item.id === campaignId);
|
||||
if (campaign) {
|
||||
commit('setActiveCampaign', campaign);
|
||||
startCampaign: async (
|
||||
{
|
||||
commit,
|
||||
rootState: {
|
||||
events: { isOpen },
|
||||
},
|
||||
},
|
||||
{ websiteToken, campaignId }
|
||||
) => {
|
||||
// Disable campaign execution if widget is opened
|
||||
if (!isOpen) {
|
||||
const { data: campaigns } = await getCampaigns(websiteToken);
|
||||
// Check campaign is disabled or not
|
||||
const campaign = campaigns.find(item => item.id === campaignId);
|
||||
if (campaign) {
|
||||
commit('setActiveCampaign', campaign);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
executeCampaign: async ({ commit }, { campaignId, websiteToken }) => {
|
||||
try {
|
||||
await triggerCampaign({ campaignId, websiteToken });
|
||||
commit('setCampaignExecuted');
|
||||
commit('setActiveCampaign', {});
|
||||
} catch (error) {
|
||||
commit('setError', true);
|
||||
}
|
||||
},
|
||||
resetCampaign: async ({ commit }) => {
|
||||
try {
|
||||
commit('setActiveCampaign', {});
|
||||
} catch (error) {
|
||||
commit('setError', true);
|
||||
|
@ -107,6 +129,9 @@ export const mutations = {
|
|||
setHasFetched($state, value) {
|
||||
Vue.set($state.uiFlags, 'hasFetched', value);
|
||||
},
|
||||
setCampaignExecuted($state) {
|
||||
Vue.set($state, 'campaignHasExecuted', true);
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -94,14 +94,28 @@ describe('#actions', () => {
|
|||
it('reset campaign if campaign id is not present in the campaign list', async () => {
|
||||
API.get.mockResolvedValue({ data: campaigns });
|
||||
await actions.startCampaign(
|
||||
{ dispatch, getters: { getCampaigns: campaigns }, commit },
|
||||
{
|
||||
dispatch,
|
||||
getters: { getCampaigns: campaigns },
|
||||
commit,
|
||||
rootState: {
|
||||
events: { isOpen: true },
|
||||
},
|
||||
},
|
||||
{ campaignId: 32 }
|
||||
);
|
||||
});
|
||||
it('start campaign if campaign id passed', async () => {
|
||||
API.get.mockResolvedValue({ data: campaigns });
|
||||
await actions.startCampaign(
|
||||
{ dispatch, getters: { getCampaigns: campaigns }, commit },
|
||||
{
|
||||
dispatch,
|
||||
getters: { getCampaigns: campaigns },
|
||||
commit,
|
||||
rootState: {
|
||||
events: { isOpen: false },
|
||||
},
|
||||
},
|
||||
{ campaignId: 1 }
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([['setActiveCampaign', campaigns[0]]]);
|
||||
|
@ -112,7 +126,10 @@ describe('#actions', () => {
|
|||
const params = { campaignId: 12, websiteToken: 'XDsafmADasd' };
|
||||
API.post.mockResolvedValue({});
|
||||
await actions.executeCampaign({ commit }, params);
|
||||
expect(commit.mock.calls).toEqual([['setActiveCampaign', {}]]);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['setCampaignExecuted'],
|
||||
['setActiveCampaign', {}],
|
||||
]);
|
||||
});
|
||||
it('sends correct actions if execute campaign API is failed', async () => {
|
||||
const params = { campaignId: 12, websiteToken: 'XDsafmADasd' };
|
||||
|
@ -121,4 +138,12 @@ describe('#actions', () => {
|
|||
expect(commit.mock.calls).toEqual([['setError', true]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resetCampaign', () => {
|
||||
it('sends correct actions if execute campaign API is success', async () => {
|
||||
API.post.mockResolvedValue({});
|
||||
await actions.resetCampaign({ commit });
|
||||
expect(commit.mock.calls).toEqual([['setActiveCampaign', {}]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -129,4 +129,17 @@ describe('#getters', () => {
|
|||
updated_at: '2021-05-03T04:53:36.354Z',
|
||||
});
|
||||
});
|
||||
it('getCampaignHasExecuted', () => {
|
||||
const state = {
|
||||
records: [],
|
||||
uiFlags: {
|
||||
isError: false,
|
||||
hasFetched: false,
|
||||
},
|
||||
activeCampaign: {},
|
||||
campaignHasExecuted: false,
|
||||
};
|
||||
|
||||
expect(getters.getCampaignHasExecuted(state)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,4 +33,12 @@ describe('#mutations', () => {
|
|||
expect(state.activeCampaign).toEqual(campaigns[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setCampaignExecuted', () => {
|
||||
it('set campaign executed flag', () => {
|
||||
const state = { records: [], uiFlags: {}, campaignHasExecuted: false };
|
||||
mutations.setCampaignExecuted(state);
|
||||
expect(state.campaignHasExecuted).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,6 +83,8 @@ import { mapGetters } from 'vuex';
|
|||
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import PreChatForm from '../components/PreChat/Form';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
|
@ -106,6 +108,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isCampaignViewClicked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -121,16 +127,24 @@ export default {
|
|||
groupedMessages: 'conversation/getGroupedConversation',
|
||||
isFetchingList: 'conversation/getIsFetchingList',
|
||||
currentUser: 'contacts/getCurrentUser',
|
||||
activeCampaign: 'campaign/getActiveCampaign',
|
||||
getCampaignHasExecuted: 'campaign/getCampaignHasExecuted',
|
||||
}),
|
||||
currentView() {
|
||||
const { email: currentUserEmail = '' } = this.currentUser;
|
||||
|
||||
if (this.isHeaderCollapsed) {
|
||||
if (this.conversationSize) {
|
||||
return 'messageView';
|
||||
}
|
||||
|
||||
if (
|
||||
this.isOnNewConversation ||
|
||||
(this.preChatFormEnabled && !currentUserEmail)
|
||||
!this.getCampaignHasExecuted &&
|
||||
((this.preChatFormEnabled &&
|
||||
!isEmptyObject(this.activeCampaign) &&
|
||||
this.preChatFormOptions.requireEmail) ||
|
||||
this.isOnNewConversation ||
|
||||
(this.preChatFormEnabled && !currentUserEmail))
|
||||
) {
|
||||
return 'preChatFormView';
|
||||
}
|
||||
|
@ -145,10 +159,13 @@ export default {
|
|||
return MAXIMUM_FILE_UPLOAD_SIZE;
|
||||
},
|
||||
isHeaderCollapsed() {
|
||||
if (!this.hasIntroText || this.conversationSize) {
|
||||
if (
|
||||
!this.hasIntroText ||
|
||||
this.conversationSize ||
|
||||
this.isCampaignViewClicked
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.isOnCollapsedView;
|
||||
},
|
||||
hasIntroText() {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
:has-fetched="hasFetched"
|
||||
:unread-message-count="unreadMessageCount"
|
||||
:show-popout-button="showPopoutButton"
|
||||
:is-campaign-view-clicked="isCampaignViewClicked"
|
||||
/>
|
||||
<unread
|
||||
v-else
|
||||
|
@ -67,6 +68,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isCampaignViewClicked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showHomePage() {
|
||||
|
|
Loading…
Reference in a new issue