From c6326993df69c8298f58a47920dba4d29dc3f2c9 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 11 Nov 2021 19:02:16 +0530 Subject: [PATCH] feat: Show pre-chat form before triggering the campaign (#3215) --- app/javascript/widget/App.vue | 31 ++++++++++-- .../widget/components/PreChat/Form.vue | 50 ++++++++++++++++--- app/javascript/widget/i18n/locale/en.json | 3 +- .../widget/store/modules/campaign.js | 35 +++++++++++-- .../modules/specs/campaign/actions.spec.js | 31 ++++++++++-- .../modules/specs/campaign/getters.spec.js | 13 +++++ .../modules/specs/campaign/mutations.spec.js | 8 +++ app/javascript/widget/views/Home.vue | 25 ++++++++-- app/javascript/widget/views/Router.vue | 5 ++ 9 files changed, 175 insertions(+), 26 deletions(-) diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue index ef9d63aac..bda48e2ec 100755 --- a/app/javascript/widget/App.vue +++ b/app/javascript/widget/App.vue @@ -8,6 +8,7 @@ :is-left-aligned="isLeftAligned" :hide-message-bubble="hideMessageBubble" :show-popout-button="showPopoutButton" + :is-campaign-view-clicked="isCampaignViewClicked" /> @@ -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') { diff --git a/app/javascript/widget/components/PreChat/Form.vue b/app/javascript/widget/components/PreChat/Form.vue index 448c33ba5..157c1f233 100644 --- a/app/javascript/widget/components/PreChat/Form.vue +++ b/app/javascript/widget/components/PreChat/Form.vue @@ -3,8 +3,11 @@ class="flex flex-1 flex-col p-6 overflow-y-auto" @submit.prevent="onSubmit" > -
- {{ options.preChatMessage }} +
+ {{ headerMessage }}
$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 { diff --git a/app/javascript/widget/store/modules/specs/campaign/actions.spec.js b/app/javascript/widget/store/modules/specs/campaign/actions.spec.js index a9bd07fa2..49f441111 100644 --- a/app/javascript/widget/store/modules/specs/campaign/actions.spec.js +++ b/app/javascript/widget/store/modules/specs/campaign/actions.spec.js @@ -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', {}]]); + }); + }); }); diff --git a/app/javascript/widget/store/modules/specs/campaign/getters.spec.js b/app/javascript/widget/store/modules/specs/campaign/getters.spec.js index 2d027426c..ceb9185e2 100644 --- a/app/javascript/widget/store/modules/specs/campaign/getters.spec.js +++ b/app/javascript/widget/store/modules/specs/campaign/getters.spec.js @@ -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); + }); }); diff --git a/app/javascript/widget/store/modules/specs/campaign/mutations.spec.js b/app/javascript/widget/store/modules/specs/campaign/mutations.spec.js index 1ca364751..7a519028e 100644 --- a/app/javascript/widget/store/modules/specs/campaign/mutations.spec.js +++ b/app/javascript/widget/store/modules/specs/campaign/mutations.spec.js @@ -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); + }); + }); }); diff --git a/app/javascript/widget/views/Home.vue b/app/javascript/widget/views/Home.vue index adb8f137d..33deb60e1 100755 --- a/app/javascript/widget/views/Home.vue +++ b/app/javascript/widget/views/Home.vue @@ -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() { diff --git a/app/javascript/widget/views/Router.vue b/app/javascript/widget/views/Router.vue index 3f21497bc..fb90cbd24 100644 --- a/app/javascript/widget/views/Router.vue +++ b/app/javascript/widget/views/Router.vue @@ -13,6 +13,7 @@ :has-fetched="hasFetched" :unread-message-count="unreadMessageCount" :show-popout-button="showPopoutButton" + :is-campaign-view-clicked="isCampaignViewClicked" />