feat: Use vue-router on widget route management (#3415)
* feat: Add vue-router to widget Co-authored-by: Pranav <pranav@chatwoot.com> * Move to dynamic imports * Move to routerMixin * Fix popup button display * Remove unnecessary import * router -> route * Fix open state * Fix issues * Remove used CSS * Fix specs * Fix specs * Fix widgetColor specs * Fix mutation specs * Fixes broken lint errors * Fixes issues with widget flow Co-authored-by: Nithin <nithin@chatwoot.com> Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
parent
991a42c417
commit
9c31d7c672
38 changed files with 617 additions and 725 deletions
|
@ -18,11 +18,11 @@ export default {
|
||||||
},
|
},
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'white',
|
default: '#c2e1ff',
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '#1976cc',
|
||||||
},
|
},
|
||||||
customStyle: {
|
customStyle: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ActionCableConnector from '../widget/helpers/actionCable';
|
||||||
import { getAlertAudio } from 'shared/helpers/AudioNotificationHelper';
|
import { getAlertAudio } from 'shared/helpers/AudioNotificationHelper';
|
||||||
import i18n from '../widget/i18n';
|
import i18n from '../widget/i18n';
|
||||||
|
|
||||||
|
import router from '../widget/router';
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
Vue.use(Vuelidate);
|
Vue.use(Vuelidate);
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ Vue.config.productionTip = false;
|
||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
window.WOOT_WIDGET = new Vue({
|
window.WOOT_WIDGET = new Vue({
|
||||||
|
router,
|
||||||
store,
|
store,
|
||||||
i18n: i18nConfig,
|
i18n: i18nConfig,
|
||||||
render: h => h(App),
|
render: h => h(App),
|
||||||
|
|
|
@ -150,11 +150,14 @@ export const IFrameHelper = {
|
||||||
onBubbleClick(bubbleState);
|
onBubbleClick(bubbleState);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
closeWindow: () => {
|
||||||
|
onBubbleClick({ toggleValue: false });
|
||||||
|
removeUnreadClass();
|
||||||
|
},
|
||||||
|
|
||||||
onBubbleToggle: isOpen => {
|
onBubbleToggle: isOpen => {
|
||||||
IFrameHelper.sendMessage('toggle-open', { isOpen });
|
IFrameHelper.sendMessage('toggle-open', { isOpen });
|
||||||
if (!isOpen) {
|
if (isOpen) {
|
||||||
IFrameHelper.events.resetUnreadMode();
|
|
||||||
} else {
|
|
||||||
IFrameHelper.pushEvent('webwidget.triggered');
|
IFrameHelper.pushEvent('webwidget.triggered');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -164,28 +167,6 @@ export const IFrameHelper = {
|
||||||
referrerHost,
|
referrerHost,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setUnreadMode: message => {
|
|
||||||
const { unreadMessageCount } = message;
|
|
||||||
const { isOpen } = window.$chatwoot;
|
|
||||||
const toggleValue = true;
|
|
||||||
|
|
||||||
if (!isOpen && unreadMessageCount > 0) {
|
|
||||||
IFrameHelper.sendMessage('set-unread-view');
|
|
||||||
onBubbleClick({ toggleValue });
|
|
||||||
addUnreadClass();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setCampaignMode: () => {
|
|
||||||
const { isOpen } = window.$chatwoot;
|
|
||||||
const toggleValue = true;
|
|
||||||
if (!isOpen) {
|
|
||||||
onBubbleClick({ toggleValue });
|
|
||||||
addUnreadClass();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateIframeHeight: message => {
|
updateIframeHeight: message => {
|
||||||
const { extraHeight = 0, isFixedHeight } = message;
|
const { extraHeight = 0, isFixedHeight } = message;
|
||||||
if (!extraHeight) return;
|
if (!extraHeight) return;
|
||||||
|
@ -193,11 +174,12 @@ export const IFrameHelper = {
|
||||||
IFrameHelper.setFrameHeightToFitContent(extraHeight, isFixedHeight);
|
IFrameHelper.setFrameHeightToFitContent(extraHeight, isFixedHeight);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetUnreadMode: () => {
|
setUnreadMode: () => {
|
||||||
IFrameHelper.sendMessage('unset-unread-view');
|
addUnreadClass();
|
||||||
removeUnreadClass();
|
onBubbleClick({ toggleValue: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetUnreadMode: () => removeUnreadClass(),
|
||||||
handleNotificationDot: event => {
|
handleNotificationDot: event => {
|
||||||
if (window.$chatwoot.hideMessageBubble) {
|
if (window.$chatwoot.hideMessageBubble) {
|
||||||
return;
|
return;
|
||||||
|
@ -253,14 +235,10 @@ export const IFrameHelper = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleCloseButton: () => {
|
toggleCloseButton: () => {
|
||||||
|
let isMobile = false;
|
||||||
if (window.matchMedia('(max-width: 668px)').matches) {
|
if (window.matchMedia('(max-width: 668px)').matches) {
|
||||||
IFrameHelper.sendMessage('toggle-close-button', {
|
isMobile = true;
|
||||||
showClose: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
IFrameHelper.sendMessage('toggle-close-button', {
|
|
||||||
showClose: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
IFrameHelper.sendMessage('toggle-close-button', { isMobile });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"arrow-clockwise-outline": "M12 4.75a7.25 7.25 0 1 0 7.201 6.406c-.068-.588.358-1.156.95-1.156.515 0 .968.358 1.03.87a9.25 9.25 0 1 1-3.432-6.116V4.25a1 1 0 1 1 2.001 0v2.698l.034.052h-.034v.25a1 1 0 0 1-1 1h-3a1 1 0 1 1 0-2h.666A7.219 7.219 0 0 0 12 4.75Z",
|
"arrow-clockwise-outline": "M12 4.75a7.25 7.25 0 1 0 7.201 6.406c-.068-.588.358-1.156.95-1.156.515 0 .968.358 1.03.87a9.25 9.25 0 1 1-3.432-6.116V4.25a1 1 0 1 1 2.001 0v2.698l.034.052h-.034v.25a1 1 0 0 1-1 1h-3a1 1 0 1 1 0-2h.666A7.219 7.219 0 0 0 12 4.75Z",
|
||||||
"arrow-right-outline": "M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z",
|
"arrow-right-outline": "M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z",
|
||||||
"attach-outline": "M11.772 3.743a6 6 0 0 1 8.66 8.302l-.19.197-8.8 8.798-.036.03a3.723 3.723 0 0 1-5.489-4.973.764.764 0 0 1 .085-.13l.054-.06.086-.088.142-.148.002.003 7.436-7.454a.75.75 0 0 1 .977-.074l.084.073a.75.75 0 0 1 .074.976l-.073.084-7.594 7.613a2.23 2.23 0 0 0 3.174 3.106l8.832-8.83A4.502 4.502 0 0 0 13 4.644l-.168.16-.013.014-9.536 9.536a.75.75 0 0 1-1.133-.977l.072-.084 9.549-9.55h.002Z",
|
"attach-outline": "M11.772 3.743a6 6 0 0 1 8.66 8.302l-.19.197-8.8 8.798-.036.03a3.723 3.723 0 0 1-5.489-4.973.764.764 0 0 1 .085-.13l.054-.06.086-.088.142-.148.002.003 7.436-7.454a.75.75 0 0 1 .977-.074l.084.073a.75.75 0 0 1 .074.976l-.073.084-7.594 7.613a2.23 2.23 0 0 0 3.174 3.106l8.832-8.83A4.502 4.502 0 0 0 13 4.644l-.168.16-.013.014-9.536 9.536a.75.75 0 0 1-1.133-.977l.072-.084 9.549-9.55h.002Z",
|
||||||
|
"chevron-left-outline": "M15.53 4.22a.75.75 0 0 1 0 1.06L8.81 12l6.72 6.72a.75.75 0 1 1-1.06 1.06l-7.25-7.25a.75.75 0 0 1 0-1.06l7.25-7.25a.75.75 0 0 1 1.06 0Z",
|
||||||
"chevron-right-outline": "M8.293 4.293a1 1 0 0 0 0 1.414L14.586 12l-6.293 6.293a1 1 0 1 0 1.414 1.414l7-7a1 1 0 0 0 0-1.414l-7-7a1 1 0 0 0-1.414 0Z",
|
"chevron-right-outline": "M8.293 4.293a1 1 0 0 0 0 1.414L14.586 12l-6.293 6.293a1 1 0 1 0 1.414 1.414l7-7a1 1 0 0 0 0-1.414l-7-7a1 1 0 0 0-1.414 0Z",
|
||||||
"dismiss-outline": "m4.397 4.554.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l6.47-6.47a.75.75 0 1 1 1.06 1.061L13.061 12l6.47 6.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-6.47 6.47a.75.75 0 0 1-1.06-1.061L10.939 12l-6.47-6.47a.75.75 0 0 1-.072-.976l.073-.084-.073.084Z",
|
"dismiss-outline": "m4.397 4.554.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l6.47-6.47a.75.75 0 1 1 1.06 1.061L13.061 12l6.47 6.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-6.47 6.47a.75.75 0 0 1-1.06-1.061L10.939 12l-6.47-6.47a.75.75 0 0 1-.072-.976l.073-.084-.073.084Z",
|
||||||
"document-outline": "M18.5 20a.5.5 0 0 1-.5.5H6a.5.5 0 0 1-.5-.5V4a.5.5 0 0 1 .5-.5h6V8a2 2 0 0 0 2 2h4.5v10Zm-5-15.379L17.378 8.5H14a.5.5 0 0 1-.5-.5V4.621Zm5.914 3.793-5.829-5.828c-.026-.026-.058-.046-.085-.07a2.072 2.072 0 0 0-.219-.18c-.04-.027-.086-.045-.128-.068-.071-.04-.141-.084-.216-.116a1.977 1.977 0 0 0-.624-.138C12.266 2.011 12.22 2 12.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9.828a2 2 0 0 0-.586-1.414Z",
|
"document-outline": "M18.5 20a.5.5 0 0 1-.5.5H6a.5.5 0 0 1-.5-.5V4a.5.5 0 0 1 .5-.5h6V8a2 2 0 0 0 2 2h4.5v10Zm-5-15.379L17.378 8.5H14a.5.5 0 0 1-.5-.5V4.621Zm5.914 3.793-5.829-5.828c-.026-.026-.058-.046-.085-.07a2.072 2.072 0 0 0-.219-.18c-.04-.027-.086-.045-.128-.068-.071-.04-.141-.084-.216-.116a1.977 1.977 0 0 0-.624-.138C12.266 2.011 12.22 2 12.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9.828a2 2 0 0 0-.586-1.414Z",
|
||||||
|
|
|
@ -1,61 +1,67 @@
|
||||||
<template>
|
<template>
|
||||||
<router
|
<div
|
||||||
:show-unread-view="showUnreadView"
|
v-if="!conversationSize && isFetchingList"
|
||||||
:show-campaign-view="showCampaignView"
|
class="flex flex-1 items-center h-full bg-black-25 justify-center"
|
||||||
:is-mobile="isMobile"
|
>
|
||||||
:has-fetched="hasFetched"
|
<spinner size="" />
|
||||||
:unread-message-count="unreadMessageCount"
|
</div>
|
||||||
:is-left-aligned="isLeftAligned"
|
<div
|
||||||
:hide-message-bubble="hideMessageBubble"
|
v-else
|
||||||
:show-popout-button="showPopoutButton"
|
class="flex flex-col justify-end h-full"
|
||||||
:is-campaign-view-clicked="isCampaignViewClicked"
|
:class="{
|
||||||
/>
|
'is-mobile': isMobile,
|
||||||
|
'is-widget-right': isRightAligned,
|
||||||
|
'is-bubble-hidden': hideMessageBubble,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions, mapMutations } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import { setHeader } from 'widget/helpers/axios';
|
import { setHeader } from 'widget/helpers/axios';
|
||||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||||
import configMixin from './mixins/configMixin';
|
import configMixin from './mixins/configMixin';
|
||||||
import availabilityMixin from 'widget/mixins/availability';
|
import availabilityMixin from 'widget/mixins/availability';
|
||||||
import Router from './views/Router';
|
|
||||||
import { getLocale } from './helpers/urlParamsHelper';
|
import { getLocale } from './helpers/urlParamsHelper';
|
||||||
import { isEmptyObject } from 'widget/helpers/utils';
|
import { isEmptyObject } from 'widget/helpers/utils';
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
import routerMixin from './mixins/routerMixin';
|
||||||
import {
|
import {
|
||||||
getExtraSpaceToScroll,
|
getExtraSpaceToScroll,
|
||||||
loadedEventConfig,
|
loadedEventConfig,
|
||||||
} from './helpers/IframeEventHelper';
|
} from './helpers/IframeEventHelper';
|
||||||
|
import {
|
||||||
|
ON_AGENT_MESSAGE_RECEIVED,
|
||||||
|
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||||
|
ON_UNREAD_MESSAGE_CLICK,
|
||||||
|
} from './constants/widgetBusEvents';
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
Router,
|
Spinner,
|
||||||
},
|
},
|
||||||
mixins: [availabilityMixin, configMixin],
|
mixins: [availabilityMixin, configMixin, routerMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showUnreadView: false,
|
|
||||||
showCampaignView: false,
|
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
hideMessageBubble: false,
|
|
||||||
widgetPosition: 'right',
|
|
||||||
showPopoutButton: false,
|
|
||||||
isWebWidgetTriggered: false,
|
|
||||||
isCampaignViewClicked: false,
|
|
||||||
isWidgetOpen: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
activeCampaign: 'campaign/getActiveCampaign',
|
||||||
|
campaigns: 'campaign/getCampaigns',
|
||||||
|
conversationSize: 'conversation/getConversationSize',
|
||||||
|
currentUser: 'contacts/getCurrentUser',
|
||||||
hasFetched: 'agent/getHasFetched',
|
hasFetched: 'agent/getHasFetched',
|
||||||
|
hideMessageBubble: 'appConfig/getHideMessageBubble',
|
||||||
|
isFetchingList: 'conversation/getIsFetchingList',
|
||||||
|
isRightAligned: 'appConfig/isRightAligned',
|
||||||
|
isWidgetOpen: 'appConfig/getIsWidgetOpen',
|
||||||
messageCount: 'conversation/getMessageCount',
|
messageCount: 'conversation/getMessageCount',
|
||||||
unreadMessageCount: 'conversation/getUnreadMessageCount',
|
unreadMessageCount: 'conversation/getUnreadMessageCount',
|
||||||
campaigns: 'campaign/getCampaigns',
|
|
||||||
activeCampaign: 'campaign/getActiveCampaign',
|
|
||||||
}),
|
}),
|
||||||
isLeftAligned() {
|
|
||||||
const isLeft = this.widgetPosition === 'left';
|
|
||||||
return isLeft;
|
|
||||||
},
|
|
||||||
isIFrame() {
|
isIFrame() {
|
||||||
return IFrameHelper.isIFrame();
|
return IFrameHelper.isIFrame();
|
||||||
},
|
},
|
||||||
|
@ -67,20 +73,11 @@ export default {
|
||||||
activeCampaign() {
|
activeCampaign() {
|
||||||
this.setCampaignView();
|
this.setCampaignView();
|
||||||
},
|
},
|
||||||
showUnreadView(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.setIframeHeight(this.isMobile);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showCampaignView(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.setIframeHeight(this.isMobile);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const { websiteToken, locale } = window.chatwootWebChannel;
|
const { websiteToken, locale, widgetColor } = window.chatwootWebChannel;
|
||||||
this.setLocale(locale);
|
this.setLocale(locale);
|
||||||
|
this.setWidgetColor(widgetColor);
|
||||||
if (this.isIFrame) {
|
if (this.isIFrame) {
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
this.sendLoadedEvent();
|
this.sendLoadedEvent();
|
||||||
|
@ -96,12 +93,15 @@ export default {
|
||||||
this.sendRNWebViewLoadedEvent();
|
this.sendRNWebViewLoadedEvent();
|
||||||
}
|
}
|
||||||
this.$store.dispatch('conversationAttributes/getAttributes');
|
this.$store.dispatch('conversationAttributes/getAttributes');
|
||||||
this.setWidgetColor(window.chatwootWebChannel);
|
|
||||||
this.registerUnreadEvents();
|
this.registerUnreadEvents();
|
||||||
this.registerCampaignEvents();
|
this.registerCampaignEvents();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('appConfig', ['setWidgetColor', 'setReferrerHost']),
|
...mapActions('appConfig', [
|
||||||
|
'setAppConfig',
|
||||||
|
'setReferrerHost',
|
||||||
|
'setWidgetColor',
|
||||||
|
]),
|
||||||
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
|
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
|
||||||
...mapActions('campaign', [
|
...mapActions('campaign', [
|
||||||
'initCampaigns',
|
'initCampaigns',
|
||||||
|
@ -109,7 +109,6 @@ export default {
|
||||||
'resetCampaign',
|
'resetCampaign',
|
||||||
]),
|
]),
|
||||||
...mapActions('agent', ['fetchAvailableAgents']),
|
...mapActions('agent', ['fetchAvailableAgents']),
|
||||||
...mapMutations('events', ['toggleOpen']),
|
|
||||||
scrollConversationToBottom() {
|
scrollConversationToBottom() {
|
||||||
const container = this.$el.querySelector('.conversation-wrap');
|
const container = this.$el.querySelector('.conversation-wrap');
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
|
@ -136,68 +135,48 @@ export default {
|
||||||
this.$root.$i18n.locale = locale;
|
this.$root.$i18n.locale = locale;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setPosition(position) {
|
|
||||||
const widgetPosition = position || 'right';
|
|
||||||
this.widgetPosition = widgetPosition;
|
|
||||||
},
|
|
||||||
setHideMessageBubble(hideBubble) {
|
|
||||||
this.hideMessageBubble = !!hideBubble;
|
|
||||||
},
|
|
||||||
registerUnreadEvents() {
|
registerUnreadEvents() {
|
||||||
bus.$on('on-agent-message-received', () => {
|
bus.$on(ON_AGENT_MESSAGE_RECEIVED, this.setUnreadView);
|
||||||
if (!this.isIFrame || this.isWidgetOpen) {
|
bus.$on(ON_UNREAD_MESSAGE_CLICK, () => {
|
||||||
this.setUserLastSeen();
|
this.replaceRoute('messages').then(() => this.unsetUnreadView());
|
||||||
}
|
|
||||||
this.setUnreadView();
|
|
||||||
});
|
|
||||||
bus.$on('on-unread-view-clicked', () => {
|
|
||||||
this.unsetUnreadView();
|
|
||||||
this.setUserLastSeen();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
registerCampaignEvents() {
|
registerCampaignEvents() {
|
||||||
bus.$on('on-campaign-view-clicked', () => {
|
bus.$on(ON_CAMPAIGN_MESSAGE_CLICK, () => {
|
||||||
this.isCampaignViewClicked = true;
|
const showPreChatForm =
|
||||||
this.showCampaignView = false;
|
this.preChatFormEnabled && this.preChatFormOptions.requireEmail;
|
||||||
this.showUnreadView = false;
|
const isUserEmailAvailable = !!this.currentUser.email;
|
||||||
this.unsetUnreadView();
|
if (showPreChatForm && !isUserEmailAvailable) {
|
||||||
this.setUserLastSeen();
|
this.replaceRoute('prechat-form');
|
||||||
// Execute campaign only if pre-chat form (and require email too) is not enabled
|
} else {
|
||||||
if (
|
this.replaceRoute('messages');
|
||||||
!(this.preChatFormEnabled && this.preChatFormOptions.requireEmail)
|
|
||||||
) {
|
|
||||||
bus.$emit('execute-campaign', this.activeCampaign.id);
|
bus.$emit('execute-campaign', this.activeCampaign.id);
|
||||||
}
|
}
|
||||||
|
this.unsetUnreadView();
|
||||||
});
|
});
|
||||||
bus.$on('execute-campaign', campaignId => {
|
bus.$on('execute-campaign', campaignId => {
|
||||||
const { websiteToken } = window.chatwootWebChannel;
|
const { websiteToken } = window.chatwootWebChannel;
|
||||||
this.executeCampaign({ campaignId, websiteToken });
|
this.executeCampaign({ campaignId, websiteToken });
|
||||||
|
this.replaceRoute('messages');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setPopoutDisplay(showPopoutButton) {
|
|
||||||
this.showPopoutButton = showPopoutButton;
|
|
||||||
},
|
|
||||||
setCampaignView() {
|
setCampaignView() {
|
||||||
const { messageCount, activeCampaign } = this;
|
const { messageCount, activeCampaign } = this;
|
||||||
const isCampaignReadyToExecute =
|
const isCampaignReadyToExecute =
|
||||||
!isEmptyObject(activeCampaign) &&
|
!isEmptyObject(activeCampaign) && !messageCount;
|
||||||
!messageCount &&
|
|
||||||
!this.isWebWidgetTriggered;
|
|
||||||
if (this.isIFrame && isCampaignReadyToExecute) {
|
if (this.isIFrame && isCampaignReadyToExecute) {
|
||||||
this.showCampaignView = true;
|
this.replaceRoute('campaigns').then(() => {
|
||||||
IFrameHelper.sendMessage({
|
this.setIframeHeight(this.isMobile);
|
||||||
event: 'setCampaignMode',
|
IFrameHelper.sendMessage({ event: 'setUnreadMode' });
|
||||||
});
|
});
|
||||||
this.setIframeHeight(this.isMobile);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setUnreadView() {
|
setUnreadView() {
|
||||||
const { unreadMessageCount } = this;
|
const { unreadMessageCount } = this;
|
||||||
if (this.isIFrame && unreadMessageCount > 0) {
|
if (this.isIFrame && unreadMessageCount > 0 && !this.isWidgetOpen) {
|
||||||
IFrameHelper.sendMessage({
|
this.replaceRoute('unread-messages').then(() => {
|
||||||
event: 'setUnreadMode',
|
this.setIframeHeight(this.isMobile);
|
||||||
unreadMessageCount,
|
IFrameHelper.sendMessage({ event: 'setUnreadMode' });
|
||||||
});
|
});
|
||||||
this.setIframeHeight(this.isMobile);
|
this.setIframeHeight(this.isMobile);
|
||||||
this.handleUnreadNotificationDot();
|
this.handleUnreadNotificationDot();
|
||||||
|
@ -222,14 +201,12 @@ export default {
|
||||||
createWidgetEvents(message) {
|
createWidgetEvents(message) {
|
||||||
const { eventName } = message;
|
const { eventName } = message;
|
||||||
const isWidgetTriggerEvent = eventName === 'webwidget.triggered';
|
const isWidgetTriggerEvent = eventName === 'webwidget.triggered';
|
||||||
this.isWebWidgetTriggered = true;
|
|
||||||
if (
|
if (
|
||||||
isWidgetTriggerEvent &&
|
isWidgetTriggerEvent &&
|
||||||
(this.showUnreadView || this.showCampaignView)
|
['unread-messages', 'campaigns'].includes(this.$route.name)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setUserLastSeen();
|
|
||||||
this.$store.dispatch('events/create', { name: eventName });
|
this.$store.dispatch('events/create', { name: eventName });
|
||||||
},
|
},
|
||||||
registerListeners() {
|
registerListeners() {
|
||||||
|
@ -242,11 +219,9 @@ export default {
|
||||||
if (message.event === 'config-set') {
|
if (message.event === 'config-set') {
|
||||||
this.setLocale(message.locale);
|
this.setLocale(message.locale);
|
||||||
this.setBubbleLabel();
|
this.setBubbleLabel();
|
||||||
this.setPosition(message.position);
|
|
||||||
this.fetchOldConversations().then(() => this.setUnreadView());
|
this.fetchOldConversations().then(() => this.setUnreadView());
|
||||||
this.setPopoutDisplay(message.showPopoutButton);
|
|
||||||
this.fetchAvailableAgents(websiteToken);
|
this.fetchAvailableAgents(websiteToken);
|
||||||
this.setHideMessageBubble(message.hideMessageBubble);
|
this.setAppConfig(message);
|
||||||
this.$store.dispatch('contacts/get');
|
this.$store.dispatch('contacts/get');
|
||||||
} else if (message.event === 'widget-visible') {
|
} else if (message.event === 'widget-visible') {
|
||||||
this.scrollConversationToBottom();
|
this.scrollConversationToBottom();
|
||||||
|
@ -260,7 +235,7 @@ export default {
|
||||||
window.referrerURL = referrerURL;
|
window.referrerURL = referrerURL;
|
||||||
this.setReferrerHost(referrerHost);
|
this.setReferrerHost(referrerHost);
|
||||||
} else if (message.event === 'toggle-close-button') {
|
} else if (message.event === 'toggle-close-button') {
|
||||||
this.isMobile = message.showClose;
|
this.isMobile = message.isMobile;
|
||||||
} else if (message.event === 'push-event') {
|
} else if (message.event === 'push-event') {
|
||||||
this.createWidgetEvents(message);
|
this.createWidgetEvents(message);
|
||||||
} else if (message.event === 'set-label') {
|
} else if (message.event === 'set-label') {
|
||||||
|
@ -282,20 +257,27 @@ export default {
|
||||||
} else if (message.event === 'set-locale') {
|
} else if (message.event === 'set-locale') {
|
||||||
this.setLocale(message.locale);
|
this.setLocale(message.locale);
|
||||||
this.setBubbleLabel();
|
this.setBubbleLabel();
|
||||||
} else if (message.event === 'set-unread-view') {
|
} else if (message.event === 'toggle-open') {
|
||||||
this.showUnreadView = true;
|
this.$store.dispatch('appConfig/toggleWidgetOpen', message.isOpen);
|
||||||
this.showCampaignView = false;
|
|
||||||
} else if (message.event === 'unset-unread-view') {
|
if (
|
||||||
// Reset campaign, If widget opened via clciking on bubble button
|
['home'].includes(this.$route.name) &&
|
||||||
if (!this.isCampaignViewClicked) {
|
message.isOpen &&
|
||||||
|
this.messageCount
|
||||||
|
) {
|
||||||
|
this.replaceRoute('messages');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!message.isOpen &&
|
||||||
|
['unread-messages', 'campaigns'].includes(this.$route.name)
|
||||||
|
) {
|
||||||
|
this.$store.dispatch('conversation/setUserLastSeen');
|
||||||
|
this.unsetUnreadView();
|
||||||
|
this.replaceRoute('home');
|
||||||
|
}
|
||||||
|
if (!message.isOpen) {
|
||||||
this.resetCampaign();
|
this.resetCampaign();
|
||||||
}
|
}
|
||||||
this.showUnreadView = false;
|
|
||||||
this.showCampaignView = false;
|
|
||||||
this.handleUnreadNotificationDot();
|
|
||||||
} else if (message.event === 'toggle-open') {
|
|
||||||
this.isWidgetOpen = message.isOpen;
|
|
||||||
this.toggleOpen();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,13 +19,6 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woot-widget-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-mobile {
|
.is-mobile {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<footer
|
||||||
<footer v-if="!hideReplyBox" class="footer">
|
v-if="!hideReplyBox"
|
||||||
<ChatInputWrap
|
class="shadow-sm rounded-lg bg-white mb-1 z-50 relative"
|
||||||
:on-send-message="handleSendMessage"
|
>
|
||||||
:on-send-attachment="handleSendAttachment"
|
<chat-input-wrap
|
||||||
/>
|
:on-send-message="handleSendMessage"
|
||||||
</footer>
|
:on-send-attachment="handleSendAttachment"
|
||||||
<div v-else>
|
/>
|
||||||
<custom-button
|
</footer>
|
||||||
class="font-medium"
|
<div v-else>
|
||||||
block
|
<custom-button
|
||||||
:bg-color="widgetColor"
|
class="font-medium"
|
||||||
:text-color="textColor"
|
block
|
||||||
@click="startNewConversation"
|
:bg-color="widgetColor"
|
||||||
>
|
:text-color="textColor"
|
||||||
{{ $t('START_NEW_CONVERSATION') }}
|
@click="startNewConversation"
|
||||||
</custom-button>
|
>
|
||||||
<custom-button
|
{{ $t('START_NEW_CONVERSATION') }}
|
||||||
v-if="showEmailTranscriptButton"
|
</custom-button>
|
||||||
type="clear"
|
<custom-button
|
||||||
class="font-normal"
|
v-if="showEmailTranscriptButton"
|
||||||
@click="sendTranscript"
|
type="clear"
|
||||||
>
|
class="font-normal"
|
||||||
{{ $t('EMAIL_TRANSCRIPT.BUTTON_TEXT') }}
|
@click="sendTranscript"
|
||||||
</custom-button>
|
>
|
||||||
</div>
|
{{ $t('EMAIL_TRANSCRIPT.BUTTON_TEXT') }}
|
||||||
|
</custom-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -115,19 +116,8 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import '~widget/assets/scss/variables.scss';
|
@import '~widget/assets/scss/variables.scss';
|
||||||
@import '~widget/assets/scss/mixins.scss';
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
background: $color-white;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 7px;
|
|
||||||
@include shadow-big;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branding {
|
.branding {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<header class="header-collapsed">
|
<header class="flex justify-between p-5 w-full">
|
||||||
<div class="header-branding">
|
<div class="flex items-center">
|
||||||
|
<button v-if="showBackButton" @click="onBackButtonClick">
|
||||||
|
<fluent-icon icon="chevron-left" size="24" />
|
||||||
|
</button>
|
||||||
<img
|
<img
|
||||||
v-if="avatarUrl"
|
v-if="avatarUrl"
|
||||||
class="inbox--avatar mr-3"
|
class="h-8 w-8 rounded-full mr-3"
|
||||||
:src="avatarUrl"
|
:src="avatarUrl"
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
/>
|
/>
|
||||||
|
@ -12,14 +15,13 @@
|
||||||
<span class="mr-1" v-html="title" />
|
<span class="mr-1" v-html="title" />
|
||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
`status-view--badge rounded-full leading-4 ${
|
`h-2 w-2 rounded-full leading-4
|
||||||
isOnline ? 'bg-green-500' : 'hidden'
|
${isOnline ? 'bg-green-500' : 'hidden'}`
|
||||||
}`
|
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs mt-1 text-black-700">
|
<div class="text-xs mt-1 text-black-700">
|
||||||
{{ replyWaitMeessage }}
|
{{ replyWaitMessage }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,15 +31,19 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import HeaderActions from './HeaderActions';
|
|
||||||
import availabilityMixin from 'widget/mixins/availability';
|
import availabilityMixin from 'widget/mixins/availability';
|
||||||
|
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||||
|
import HeaderActions from './HeaderActions';
|
||||||
|
import routerMixin from 'widget/mixins/routerMixin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatHeader',
|
name: 'ChatHeader',
|
||||||
components: {
|
components: {
|
||||||
|
FluentIcon,
|
||||||
HeaderActions,
|
HeaderActions,
|
||||||
},
|
},
|
||||||
mixins: [availabilityMixin],
|
mixins: [availabilityMixin, routerMixin],
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: {
|
avatarUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -51,15 +57,17 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
showBackButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
availableAgents: {
|
availableAgents: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({ widgetColor: 'appConfig/getWidgetColor' }),
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
|
||||||
}),
|
|
||||||
isOnline() {
|
isOnline() {
|
||||||
const { workingHoursEnabled } = this.channelConfig;
|
const { workingHoursEnabled } = this.channelConfig;
|
||||||
const anyAgentOnline = this.availableAgents.length > 0;
|
const anyAgentOnline = this.availableAgents.length > 0;
|
||||||
|
@ -69,47 +77,16 @@ export default {
|
||||||
}
|
}
|
||||||
return anyAgentOnline;
|
return anyAgentOnline;
|
||||||
},
|
},
|
||||||
replyWaitMeessage() {
|
replyWaitMessage() {
|
||||||
return this.isOnline
|
return this.isOnline
|
||||||
? this.replyTimeStatus
|
? this.replyTimeStatus
|
||||||
: this.$t('TEAM_AVAILABILITY.OFFLINE');
|
: this.$t('TEAM_AVAILABILITY.OFFLINE');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
onBackButtonClick() {
|
||||||
|
this.replaceRoute('home');
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import '~widget/assets/scss/variables.scss';
|
|
||||||
@import '~widget/assets/scss/mixins.scss';
|
|
||||||
|
|
||||||
.header-collapsed {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: $space-two $space-medium;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.header-branding {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inbox--avatar {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-view--badge {
|
|
||||||
height: $space-small;
|
|
||||||
width: $space-small;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<header class="header-expanded bg-white py-8 px-6 relative box-border w-full">
|
<header class="header-expanded bg-white py-6 px-5 relative box-border w-full">
|
||||||
<div
|
<div
|
||||||
class="flex items-start"
|
class="flex items-start"
|
||||||
:class="[avatarUrl ? 'justify-between' : 'justify-end']"
|
:class="[avatarUrl ? 'justify-between' : 'justify-end']"
|
||||||
>
|
>
|
||||||
<img v-if="avatarUrl" class="logo" :src="avatarUrl" />
|
<img v-if="avatarUrl" class="h-12 rounded-full" :src="avatarUrl" />
|
||||||
<header-actions :show-popout-button="showPopoutButton" />
|
<header-actions :show-popout-button="showPopoutButton" />
|
||||||
</div>
|
</div>
|
||||||
<h2
|
<h2
|
||||||
class="text-slate-900 mt-6 text-4xl mb-3 font-normal"
|
class="text-slate-900 mt-5 text-3xl mb-3 font-normal"
|
||||||
v-html="introHeading"
|
v-html="introHeading"
|
||||||
/>
|
/>
|
||||||
<p class="text-lg text-black-700 leading-normal" v-html="introBody" />
|
<p class="text-lg text-black-700 leading-normal" v-html="introBody" />
|
||||||
|
@ -48,17 +48,3 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import '~widget/assets/scss/mixins.scss';
|
|
||||||
|
|
||||||
$logo-size: 56px;
|
|
||||||
|
|
||||||
.header-expanded {
|
|
||||||
.logo {
|
|
||||||
width: $logo-size;
|
|
||||||
height: $logo-size;
|
|
||||||
border-radius: $logo-size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
|
isWidgetOpen: 'appConfig/getIsWidgetOpen',
|
||||||
}),
|
}),
|
||||||
showAttachment() {
|
showAttachment() {
|
||||||
return this.hasAttachmentsEnabled && this.userInput.length === 0;
|
return this.hasAttachmentsEnabled && this.userInput.length === 0;
|
||||||
|
@ -97,13 +98,10 @@ export default {
|
||||||
showSendButton() {
|
showSendButton() {
|
||||||
return this.userInput.length > 0;
|
return this.userInput.length > 0;
|
||||||
},
|
},
|
||||||
isOpen() {
|
|
||||||
return this.$store.state.events.isOpen;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isOpen(isOpen) {
|
isWidgetOpen(isWidgetOpen) {
|
||||||
if (isOpen) {
|
if (isWidgetOpen) {
|
||||||
this.focusInput();
|
this.focusInput();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -113,7 +111,7 @@ export default {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keypress', this.handleEnterKeyPress);
|
document.addEventListener('keypress', this.handleEnterKeyPress);
|
||||||
if (this.isOpen) {
|
if (this.isWidgetOpen) {
|
||||||
this.focusInput();
|
this.focusInput();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -67,9 +67,7 @@ export default {
|
||||||
},
|
},
|
||||||
closeWindow() {
|
closeWindow() {
|
||||||
if (IFrameHelper.isIFrame()) {
|
if (IFrameHelper.isIFrame()) {
|
||||||
IFrameHelper.sendMessage({
|
IFrameHelper.sendMessage({ event: 'closeWindow' });
|
||||||
event: 'toggleBubble',
|
|
||||||
});
|
|
||||||
} else if (RNHelper.isRNWebView) {
|
} else if (RNHelper.isRNWebView) {
|
||||||
RNHelper.sendMessage({ type: 'close-widget' });
|
RNHelper.sendMessage({ type: 'close-widget' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<form-text-area
|
<form-text-area
|
||||||
v-if="!activeCampaignExist"
|
v-if="!hasActiveCampaign"
|
||||||
v-model="message"
|
v-model="message"
|
||||||
class="my-5"
|
class="my-5"
|
||||||
:label="$t('PRE_CHAT_FORM.FIELDS.MESSAGE.LABEL')"
|
:label="$t('PRE_CHAT_FORM.FIELDS.MESSAGE.LABEL')"
|
||||||
|
@ -63,7 +63,7 @@ import { mapGetters } from 'vuex';
|
||||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||||
import { required, minLength, email } from 'vuelidate/lib/validators';
|
import { required, minLength, email } from 'vuelidate/lib/validators';
|
||||||
import { isEmptyObject } from 'widget/helpers/utils';
|
import { isEmptyObject } from 'widget/helpers/utils';
|
||||||
|
import routerMixin from 'widget/mixins/routerMixin';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
FormInput,
|
FormInput,
|
||||||
|
@ -71,6 +71,7 @@ export default {
|
||||||
CustomButton,
|
CustomButton,
|
||||||
Spinner,
|
Spinner,
|
||||||
},
|
},
|
||||||
|
mixins: [routerMixin],
|
||||||
props: {
|
props: {
|
||||||
options: {
|
options: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -95,7 +96,7 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// For campaign, message field is not required
|
// For campaign, message field is not required
|
||||||
if (this.activeCampaignExist) {
|
if (this.hasActiveCampaign) {
|
||||||
return identityValidations;
|
return identityValidations;
|
||||||
}
|
}
|
||||||
if (this.options.requireEmail) {
|
if (this.options.requireEmail) {
|
||||||
|
@ -122,14 +123,14 @@ export default {
|
||||||
textColor() {
|
textColor() {
|
||||||
return getContrastingTextColor(this.widgetColor);
|
return getContrastingTextColor(this.widgetColor);
|
||||||
},
|
},
|
||||||
activeCampaignExist() {
|
hasActiveCampaign() {
|
||||||
return !isEmptyObject(this.activeCampaign);
|
return !isEmptyObject(this.activeCampaign);
|
||||||
},
|
},
|
||||||
shouldShowHeaderMessage() {
|
shouldShowHeaderMessage() {
|
||||||
return this.activeCampaignExist || this.options.preChatMessage;
|
return this.hasActiveCampaign || this.options.preChatMessage;
|
||||||
},
|
},
|
||||||
headerMessage() {
|
headerMessage() {
|
||||||
if (this.activeCampaignExist) {
|
if (this.hasActiveCampaign) {
|
||||||
return this.$t('PRE_CHAT_FORM.CAMPAIGN_HEADER');
|
return this.$t('PRE_CHAT_FORM.CAMPAIGN_HEADER');
|
||||||
}
|
}
|
||||||
return this.options.preChatMessage;
|
return this.options.preChatMessage;
|
||||||
|
@ -141,22 +142,12 @@ export default {
|
||||||
if (this.$v.$invalid) {
|
if (this.$v.$invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check any active campaign exist or not
|
this.$emit('submit', {
|
||||||
if (this.activeCampaignExist) {
|
fullName: this.fullName,
|
||||||
bus.$emit('execute-campaign', this.activeCampaign.id);
|
emailAddress: this.emailAddress,
|
||||||
this.$store.dispatch('contacts/update', {
|
message: this.message,
|
||||||
user: {
|
activeCampaignId: this.activeCampaign.id,
|
||||||
email: this.emailAddress,
|
});
|
||||||
name: this.fullName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$store.dispatch('conversation/createConversation', {
|
|
||||||
fullName: this.fullName,
|
|
||||||
emailAddress: this.emailAddress,
|
|
||||||
message: this.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="px-4">
|
<div class="px-5">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div class="text-black-700">
|
<div class="text-black-700">
|
||||||
<div class="text-base leading-5 font-medium mb-1">
|
<div class="text-base leading-5 font-medium mb-1">
|
||||||
|
@ -22,7 +22,9 @@
|
||||||
:text-color="textColor"
|
:text-color="textColor"
|
||||||
@click="startConversation"
|
@click="startConversation"
|
||||||
>
|
>
|
||||||
{{ $t('START_CONVERSATION') }}
|
{{
|
||||||
|
hasConversation ? $t('CONTINUE_CONVERSATION') : $t('START_CONVERSATION')
|
||||||
|
}}
|
||||||
</custom-button>
|
</custom-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -47,6 +49,10 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
hasConversation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ widgetColor: 'appConfig/getWidgetColor' }),
|
...mapGetters({ widgetColor: 'appConfig/getWidgetColor' }),
|
||||||
|
|
|
@ -21,6 +21,10 @@ import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||||
import configMixin from '../mixins/configMixin';
|
import configMixin from '../mixins/configMixin';
|
||||||
import { isEmptyObject } from 'widget/helpers/utils';
|
import { isEmptyObject } from 'widget/helpers/utils';
|
||||||
|
import {
|
||||||
|
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||||
|
ON_UNREAD_MESSAGE_CLICK,
|
||||||
|
} from '../constants/widgetBusEvents';
|
||||||
export default {
|
export default {
|
||||||
name: 'UnreadMessage',
|
name: 'UnreadMessage',
|
||||||
components: { Thumbnail },
|
components: { Thumbnail },
|
||||||
|
@ -82,9 +86,9 @@ export default {
|
||||||
},
|
},
|
||||||
onClickMessage() {
|
onClickMessage() {
|
||||||
if (this.campaignId) {
|
if (this.campaignId) {
|
||||||
bus.$emit('on-campaign-view-clicked', this.campaignId);
|
bus.$emit(ON_CAMPAIGN_MESSAGE_CLICK, this.campaignId);
|
||||||
} else {
|
} else {
|
||||||
bus.$emit('on-unread-view-clicked');
|
bus.$emit(ON_UNREAD_MESSAGE_CLICK);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="unread-wrap">
|
<div class="unread-wrap">
|
||||||
<div class="close-unread-wrap">
|
<div class="close-unread-wrap">
|
||||||
<button
|
<button class="button small close-unread-button" @click="closeFullView">
|
||||||
v-if="showCloseButton"
|
<span class="flex items-center">
|
||||||
class="button small close-unread-button"
|
|
||||||
@click="closeFullView"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<fluent-icon class="mr-1" icon="dismiss" size="12" />
|
<fluent-icon class="mr-1" icon="dismiss" size="12" />
|
||||||
{{ $t('UNREAD_VIEW.CLOSE_MESSAGES_BUTTON') }}
|
{{ $t('UNREAD_VIEW.CLOSE_MESSAGES_BUTTON') }}
|
||||||
</div>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="unread-messages">
|
<div class="unread-messages">
|
||||||
<unread-message
|
<unread-message
|
||||||
v-for="(message, index) in allMessages"
|
v-for="(message, index) in messages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
:message-type="message.messageType"
|
:message-type="message.messageType"
|
||||||
:message-id="message.id"
|
:message-id="message.id"
|
||||||
|
@ -29,12 +25,12 @@
|
||||||
<button
|
<button
|
||||||
v-if="unreadMessageCount"
|
v-if="unreadMessageCount"
|
||||||
class="button clear-button"
|
class="button clear-button"
|
||||||
@click="openFullView"
|
@click="openConversationView"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<span class="flex items-center">
|
||||||
<fluent-icon class="mr-2" size="16" icon="arrow-right" />
|
<fluent-icon class="mr-2" size="16" icon="arrow-right" />
|
||||||
{{ $t('UNREAD_VIEW.VIEW_MESSAGES_BUTTON') }}
|
{{ $t('UNREAD_VIEW.VIEW_MESSAGES_BUTTON') }}
|
||||||
</div>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +40,7 @@
|
||||||
import { IFrameHelper } from 'widget/helpers/utils';
|
import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import configMixin from '../mixins/configMixin';
|
import configMixin from '../mixins/configMixin';
|
||||||
|
import { ON_UNREAD_MESSAGE_CLICK } from '../constants/widgetBusEvents';
|
||||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||||
import UnreadMessage from 'widget/components/UnreadMessage.vue';
|
import UnreadMessage from 'widget/components/UnreadMessage.vue';
|
||||||
|
|
||||||
|
@ -56,58 +52,25 @@ export default {
|
||||||
},
|
},
|
||||||
mixins: [configMixin],
|
mixins: [configMixin],
|
||||||
props: {
|
props: {
|
||||||
hasFetched: {
|
messages: {
|
||||||
type: Boolean,
|
type: Array,
|
||||||
default: false,
|
required: true,
|
||||||
},
|
|
||||||
unreadMessageCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
hideMessageBubble: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showUnreadView: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({ unreadMessageCount: 'conversation/getUnreadMessageCount' }),
|
||||||
unreadMessages: 'conversation/getUnreadTextMessages',
|
|
||||||
campaign: 'campaign/getActiveCampaign',
|
|
||||||
}),
|
|
||||||
showCloseButton() {
|
|
||||||
return this.unreadMessageCount;
|
|
||||||
},
|
|
||||||
sender() {
|
sender() {
|
||||||
const [firstMessage] = this.unreadMessages;
|
const [firstMessage] = this.messages;
|
||||||
return firstMessage.sender || {};
|
return firstMessage.sender || {};
|
||||||
},
|
},
|
||||||
allMessages() {
|
|
||||||
if (this.showUnreadView) {
|
|
||||||
return this.unreadMessages;
|
|
||||||
}
|
|
||||||
const { sender, id: campaignId, message: content } = this.campaign;
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
content,
|
|
||||||
sender,
|
|
||||||
campaignId,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openFullView() {
|
openConversationView() {
|
||||||
bus.$emit('on-unread-view-clicked');
|
bus.$emit(ON_UNREAD_MESSAGE_CLICK);
|
||||||
},
|
},
|
||||||
closeFullView() {
|
closeFullView() {
|
||||||
if (IFrameHelper.isIFrame()) {
|
if (IFrameHelper.isIFrame()) {
|
||||||
IFrameHelper.sendMessage({
|
IFrameHelper.sendMessage({ event: 'toggleBubble' });
|
||||||
event: 'toggleBubble',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getMessageContent(message) {
|
getMessageContent(message) {
|
118
app/javascript/widget/components/layouts/ViewWithHeader.vue
Normal file
118
app/javascript/widget/components/layouts/ViewWithHeader.vue
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="w-full h-full bg-slate-50 flex flex-col"
|
||||||
|
@keydown.esc="closeWindow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="header-wrap bg-white"
|
||||||
|
:class="{ expanded: !isHeaderCollapsed, collapsed: isHeaderCollapsed }"
|
||||||
|
>
|
||||||
|
<transition
|
||||||
|
enter-active-class="transition-all delay-200 duration-300 ease-in"
|
||||||
|
leave-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-class="opacity-0"
|
||||||
|
enter-to-class="opacity-100"
|
||||||
|
leave-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<chat-header-expanded
|
||||||
|
v-if="!isHeaderCollapsed"
|
||||||
|
:intro-heading="channelConfig.welcomeTitle"
|
||||||
|
:intro-body="channelConfig.welcomeTagline"
|
||||||
|
:avatar-url="channelConfig.avatarUrl"
|
||||||
|
:show-popout-button="appConfig.showPopoutButton"
|
||||||
|
/>
|
||||||
|
<chat-header
|
||||||
|
v-if="isHeaderCollapsed"
|
||||||
|
:title="channelConfig.websiteName"
|
||||||
|
:avatar-url="channelConfig.avatarUrl"
|
||||||
|
:show-popout-button="appConfig.showPopoutButton"
|
||||||
|
:available-agents="availableAgents"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<banner />
|
||||||
|
<transition
|
||||||
|
enter-active-class="transition-all delay-300 duration-300 ease-in"
|
||||||
|
leave-active-class="transition-all duration-200 ease-out"
|
||||||
|
enter-class="opacity-0"
|
||||||
|
enter-to-class="opacity-100"
|
||||||
|
leave-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<router-view />
|
||||||
|
</transition>
|
||||||
|
<branding />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Banner from '../Banner.vue';
|
||||||
|
import Branding from 'shared/components/Branding.vue';
|
||||||
|
import ChatHeader from '../ChatHeader.vue';
|
||||||
|
import ChatHeaderExpanded from '../ChatHeaderExpanded.vue';
|
||||||
|
import configMixin from '../../mixins/configMixin';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Banner,
|
||||||
|
Branding,
|
||||||
|
ChatHeader,
|
||||||
|
ChatHeaderExpanded,
|
||||||
|
},
|
||||||
|
mixins: [configMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showPopoutButton: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
availableAgents: 'agent/availableAgents',
|
||||||
|
appConfig: 'appConfig/getAppConfig',
|
||||||
|
}),
|
||||||
|
isHeaderCollapsed() {
|
||||||
|
if (!this.hasIntroText) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.$route.name !== 'home';
|
||||||
|
},
|
||||||
|
hasIntroText() {
|
||||||
|
return (
|
||||||
|
this.channelConfig.welcomeTitle || this.channelConfig.welcomeTagline
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeWindow() {
|
||||||
|
IFrameHelper.sendMessage({ event: 'closeWindow' });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~widget/assets/scss/variables';
|
||||||
|
@import '~widget/assets/scss/mixins';
|
||||||
|
|
||||||
|
.header-wrap {
|
||||||
|
border-radius: $space-normal $space-normal 0 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: max-height 300ms;
|
||||||
|
z-index: 99;
|
||||||
|
@include shadow-large;
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
max-height: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
max-height: 4.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-device-width: 320px) and (max-device-width: 667px) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
3
app/javascript/widget/constants/widgetBusEvents.js
Normal file
3
app/javascript/widget/constants/widgetBusEvents.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const ON_AGENT_MESSAGE_RECEIVED = 'ON_AGENT_MESSAGE_RECEIVED';
|
||||||
|
export const ON_UNREAD_MESSAGE_CLICK = 'ON_UNREAD_MESSAGE_CLICK';
|
||||||
|
export const ON_CAMPAIGN_MESSAGE_CLICK = 'ON_CAMPAIGN_MESSAGE_CLICK';
|
|
@ -1,5 +1,6 @@
|
||||||
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
|
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
|
||||||
import { playNewMessageNotificationInWidget } from 'shared/helpers/AudioNotificationHelper';
|
import { playNewMessageNotificationInWidget } from 'shared/helpers/AudioNotificationHelper';
|
||||||
|
import { ON_AGENT_MESSAGE_RECEIVED } from '../constants/widgetBusEvents';
|
||||||
|
|
||||||
class ActionCableConnector extends BaseActionCableConnector {
|
class ActionCableConnector extends BaseActionCableConnector {
|
||||||
constructor(app, pubsubToken) {
|
constructor(app, pubsubToken) {
|
||||||
|
@ -22,9 +23,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
onMessageCreated = data => {
|
onMessageCreated = data => {
|
||||||
this.app.$store
|
this.app.$store
|
||||||
.dispatch('conversation/addOrUpdateMessage', data)
|
.dispatch('conversation/addOrUpdateMessage', data)
|
||||||
.then(() => {
|
.then(() => window.bus.$emit(ON_AGENT_MESSAGE_RECEIVED));
|
||||||
window.bus.$emit('on-agent-message-received');
|
|
||||||
});
|
|
||||||
if (data.sender_type === 'User') {
|
if (data.sender_type === 'User') {
|
||||||
playNewMessageNotificationInWidget();
|
playNewMessageNotificationInWidget();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,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",
|
||||||
|
"CONTINUE_CONVERSATION": "Continue conversation",
|
||||||
"START_NEW_CONVERSATION": "Start a new conversation",
|
"START_NEW_CONVERSATION": "Start a new conversation",
|
||||||
"UNREAD_VIEW": {
|
"UNREAD_VIEW": {
|
||||||
"VIEW_MESSAGES_BUTTON": "See new messages",
|
"VIEW_MESSAGES_BUTTON": "See new messages",
|
||||||
|
|
|
@ -25,10 +25,8 @@ export default {
|
||||||
let requireEmail = false;
|
let requireEmail = false;
|
||||||
let preChatMessage = '';
|
let preChatMessage = '';
|
||||||
const options = window.chatwootWebChannel.preChatFormOptions || {};
|
const options = window.chatwootWebChannel.preChatFormOptions || {};
|
||||||
if (!this.isOnNewConversation) {
|
requireEmail = options.require_email;
|
||||||
requireEmail = options.require_email;
|
preChatMessage = options.pre_chat_message;
|
||||||
preChatMessage = options.pre_chat_message;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
requireEmail,
|
requireEmail,
|
||||||
preChatMessage,
|
preChatMessage,
|
||||||
|
|
10
app/javascript/widget/mixins/routerMixin.js
Normal file
10
app/javascript/widget/mixins/routerMixin.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
async replaceRoute(name) {
|
||||||
|
if (this.$route.name !== name) {
|
||||||
|
return this.$router.replace({ name });
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,24 +1,42 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import Home from './views/Home.vue';
|
import ViewWithHeader from './components/layouts/ViewWithHeader.vue';
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
export default new Router({
|
export default new Router({
|
||||||
|
mode: 'hash',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/unread-messages',
|
||||||
name: 'home',
|
name: 'unread-messages',
|
||||||
component: Home,
|
component: () => import('./views/UnreadMessages.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/campaigns',
|
||||||
|
name: 'campaigns',
|
||||||
|
component: () => import('./views/Campaigns.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: ViewWithHeader,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'home',
|
||||||
|
component: () => import('./views/Home.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/prechat-form',
|
||||||
|
name: 'prechat-form',
|
||||||
|
component: () => import('./views/PreChatForm.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/messages',
|
||||||
|
name: 'messages',
|
||||||
|
component: () => import('./views/Messages.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// path: '/about',
|
|
||||||
// name: 'about',
|
|
||||||
// // route level code-splitting
|
|
||||||
// // this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// // which is lazy-loaded when the route is visited.
|
|
||||||
// component: () =>
|
|
||||||
// import(/* webpackChunkName: "about" */ './views/About.vue'),
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,43 @@
|
||||||
import { SET_REFERRER_HOST, SET_WIDGET_COLOR } from '../types';
|
import {
|
||||||
|
SET_REFERRER_HOST,
|
||||||
|
SET_WIDGET_APP_CONFIG,
|
||||||
|
SET_WIDGET_COLOR,
|
||||||
|
TOGGLE_WIDGET_OPEN,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
|
showPopoutButton: false,
|
||||||
|
hideMessageBubble: false,
|
||||||
|
position: 'right',
|
||||||
|
isWebWidgetTriggered: false,
|
||||||
|
isCampaignViewClicked: false,
|
||||||
|
isWidgetOpen: false,
|
||||||
widgetColor: '',
|
widgetColor: '',
|
||||||
referrerHost: '',
|
referrerHost: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
getAppConfig: $state => $state,
|
||||||
|
isRightAligned: $state => $state.position === 'right',
|
||||||
|
getHideMessageBubble: $state => $state.hideMessageBubble,
|
||||||
|
getIsWidgetOpen: $state => $state.isWidgetOpen,
|
||||||
getWidgetColor: $state => $state.widgetColor,
|
getWidgetColor: $state => $state.widgetColor,
|
||||||
getReferrerHost: $state => $state.referrerHost,
|
getReferrerHost: $state => $state.referrerHost,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
setWidgetColor({ commit }, data) {
|
setAppConfig({ commit }, { showPopoutButton, position, hideMessageBubble }) {
|
||||||
commit(SET_WIDGET_COLOR, data);
|
commit(SET_WIDGET_APP_CONFIG, {
|
||||||
|
showPopoutButton: !!showPopoutButton,
|
||||||
|
position: position || 'right',
|
||||||
|
hideMessageBubble: !!hideMessageBubble,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
toggleWidgetOpen({ commit }, isWidgetOpen) {
|
||||||
|
commit(TOGGLE_WIDGET_OPEN, isWidgetOpen);
|
||||||
|
},
|
||||||
|
setWidgetColor({ commit }, widgetColor) {
|
||||||
|
commit(SET_WIDGET_COLOR, widgetColor);
|
||||||
},
|
},
|
||||||
setReferrerHost({ commit }, referrerHost) {
|
setReferrerHost({ commit }, referrerHost) {
|
||||||
commit(SET_REFERRER_HOST, referrerHost);
|
commit(SET_REFERRER_HOST, referrerHost);
|
||||||
|
@ -20,8 +45,16 @@ export const actions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
[SET_WIDGET_COLOR]($state, data) {
|
[SET_WIDGET_APP_CONFIG]($state, data) {
|
||||||
$state.widgetColor = data.widgetColor;
|
$state.showPopoutButton = data.showPopoutButton;
|
||||||
|
$state.position = data.position;
|
||||||
|
$state.hideMessageBubble = data.hideMessageBubble;
|
||||||
|
},
|
||||||
|
[TOGGLE_WIDGET_OPEN]($state, isWidgetOpen) {
|
||||||
|
$state.isWidgetOpen = isWidgetOpen;
|
||||||
|
},
|
||||||
|
[SET_WIDGET_COLOR]($state, widgetColor) {
|
||||||
|
$state.widgetColor = widgetColor;
|
||||||
},
|
},
|
||||||
[SET_REFERRER_HOST]($state, referrerHost) {
|
[SET_REFERRER_HOST]($state, referrerHost) {
|
||||||
$state.referrerHost = referrerHost;
|
$state.referrerHost = referrerHost;
|
||||||
|
|
|
@ -9,10 +9,8 @@ const state = {
|
||||||
records: [],
|
records: [],
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isError: false,
|
isError: false,
|
||||||
hasFetched: false,
|
|
||||||
},
|
},
|
||||||
activeCampaign: {},
|
activeCampaign: {},
|
||||||
campaignHasExecuted: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetCampaignTimers = (
|
const resetCampaignTimers = (
|
||||||
|
@ -32,10 +30,8 @@ const resetCampaignTimers = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getHasFetched: $state => $state.uiFlags.hasFetched,
|
|
||||||
getCampaigns: $state => $state.records,
|
getCampaigns: $state => $state.records,
|
||||||
getActiveCampaign: $state => $state.activeCampaign,
|
getActiveCampaign: $state => $state.activeCampaign,
|
||||||
getCampaignHasExecuted: $state => $state.campaignHasExecuted,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
@ -47,7 +43,6 @@ export const actions = {
|
||||||
const { data: campaigns } = await getCampaigns(websiteToken);
|
const { data: campaigns } = await getCampaigns(websiteToken);
|
||||||
commit('setCampaigns', campaigns);
|
commit('setCampaigns', campaigns);
|
||||||
commit('setError', false);
|
commit('setError', false);
|
||||||
commit('setHasFetched', true);
|
|
||||||
resetCampaignTimers(
|
resetCampaignTimers(
|
||||||
campaigns,
|
campaigns,
|
||||||
currentURL,
|
currentURL,
|
||||||
|
@ -56,7 +51,6 @@ export const actions = {
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
commit('setError', true);
|
commit('setError', true);
|
||||||
commit('setHasFetched', true);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initCampaigns: async (
|
initCampaigns: async (
|
||||||
|
@ -82,13 +76,13 @@ export const actions = {
|
||||||
{
|
{
|
||||||
commit,
|
commit,
|
||||||
rootState: {
|
rootState: {
|
||||||
events: { isOpen },
|
appConfig: { isWidgetOpen },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ websiteToken, campaignId }
|
{ websiteToken, campaignId }
|
||||||
) => {
|
) => {
|
||||||
// Disable campaign execution if widget is opened
|
// Disable campaign execution if widget is opened
|
||||||
if (!isOpen) {
|
if (!isWidgetOpen) {
|
||||||
const { data: campaigns } = await getCampaigns(websiteToken);
|
const { data: campaigns } = await getCampaigns(websiteToken);
|
||||||
// Check campaign is disabled or not
|
// Check campaign is disabled or not
|
||||||
const campaign = campaigns.find(item => item.id === campaignId);
|
const campaign = campaigns.find(item => item.id === campaignId);
|
||||||
|
@ -100,11 +94,21 @@ export const actions = {
|
||||||
|
|
||||||
executeCampaign: async ({ commit }, { campaignId, websiteToken }) => {
|
executeCampaign: async ({ commit }, { campaignId, websiteToken }) => {
|
||||||
try {
|
try {
|
||||||
|
commit(
|
||||||
|
'conversation/setConversationUIFlag',
|
||||||
|
{ isCreating: true },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
await triggerCampaign({ campaignId, websiteToken });
|
await triggerCampaign({ campaignId, websiteToken });
|
||||||
commit('setCampaignExecuted');
|
|
||||||
commit('setActiveCampaign', {});
|
commit('setActiveCampaign', {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
commit('setError', true);
|
commit('setError', true);
|
||||||
|
} finally {
|
||||||
|
commit(
|
||||||
|
'conversation/setConversationUIFlag',
|
||||||
|
{ isCreating: false },
|
||||||
|
{ root: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetCampaign: async ({ commit }) => {
|
resetCampaign: async ({ commit }) => {
|
||||||
|
@ -126,12 +130,6 @@ export const mutations = {
|
||||||
setError($state, value) {
|
setError($state, value) {
|
||||||
Vue.set($state.uiFlags, 'isError', value);
|
Vue.set($state.uiFlags, 'isError', value);
|
||||||
},
|
},
|
||||||
setHasFetched($state, value) {
|
|
||||||
Vue.set($state.uiFlags, 'hasFetched', value);
|
|
||||||
},
|
|
||||||
setCampaignExecuted($state) {
|
|
||||||
Vue.set($state, 'campaignHasExecuted', true);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import events from 'widget/api/events';
|
import events from 'widget/api/events';
|
||||||
|
|
||||||
const state = {
|
|
||||||
isOpen: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
create: async (_, { name }) => {
|
create: async (_, { name }) => {
|
||||||
try {
|
try {
|
||||||
|
@ -14,16 +10,10 @@ const actions = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
toggleOpen($state) {
|
|
||||||
$state.isOpen = !$state.isOpen;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state,
|
state: {},
|
||||||
getters: {},
|
getters: {},
|
||||||
actions,
|
actions,
|
||||||
mutations,
|
mutations: {},
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#setError', () => {
|
describe('#setHasFetched', () => {
|
||||||
it('set fetched flag', () => {
|
it('set fetched flag', () => {
|
||||||
const state = { records: [], uiFlags: {} };
|
const state = { records: [], uiFlags: {} };
|
||||||
mutations.setHasFetched(state, true);
|
mutations.setHasFetched(state, true);
|
||||||
|
|
|
@ -13,10 +13,8 @@ describe('#actions', () => {
|
||||||
|
|
||||||
describe('#setWidgetColor', () => {
|
describe('#setWidgetColor', () => {
|
||||||
it('creates actions properly', () => {
|
it('creates actions properly', () => {
|
||||||
actions.setWidgetColor({ commit }, { widgetColor: '#eaeaea' });
|
actions.setWidgetColor({ commit }, '#eaeaea');
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([['SET_WIDGET_COLOR', '#eaeaea']]);
|
||||||
['SET_WIDGET_COLOR', { widgetColor: '#eaeaea' }],
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe('#mutations', () => {
|
||||||
describe('#SET_WIDGET_COLOR', () => {
|
describe('#SET_WIDGET_COLOR', () => {
|
||||||
it('sets widget color properly', () => {
|
it('sets widget color properly', () => {
|
||||||
const state = { widgetColor: '' };
|
const state = { widgetColor: '' };
|
||||||
mutations.SET_WIDGET_COLOR(state, { widgetColor: '#00bcd4' });
|
mutations.SET_WIDGET_COLOR(state, '#00bcd4');
|
||||||
expect(state.widgetColor).toEqual('#00bcd4');
|
expect(state.widgetColor).toEqual('#00bcd4');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,6 @@ describe('#actions', () => {
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
['setCampaigns', campaigns],
|
['setCampaigns', campaigns],
|
||||||
['setError', false],
|
['setError', false],
|
||||||
['setHasFetched', true],
|
|
||||||
]);
|
]);
|
||||||
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
|
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
|
@ -50,10 +49,7 @@ describe('#actions', () => {
|
||||||
isInBusinessHours: true,
|
isInBusinessHours: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([['setError', true]]);
|
||||||
['setError', true],
|
|
||||||
['setHasFetched', true],
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#initCampaigns', () => {
|
describe('#initCampaigns', () => {
|
||||||
|
@ -99,7 +95,7 @@ describe('#actions', () => {
|
||||||
getters: { getCampaigns: campaigns },
|
getters: { getCampaigns: campaigns },
|
||||||
commit,
|
commit,
|
||||||
rootState: {
|
rootState: {
|
||||||
events: { isOpen: true },
|
appConfig: { isWidgetOpen: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ campaignId: 32 }
|
{ campaignId: 32 }
|
||||||
|
@ -113,7 +109,7 @@ describe('#actions', () => {
|
||||||
getters: { getCampaigns: campaigns },
|
getters: { getCampaigns: campaigns },
|
||||||
commit,
|
commit,
|
||||||
rootState: {
|
rootState: {
|
||||||
events: { isOpen: false },
|
appConfig: { isWidgetOpen: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ campaignId: 1 }
|
{ campaignId: 1 }
|
||||||
|
@ -127,15 +123,52 @@ describe('#actions', () => {
|
||||||
API.post.mockResolvedValue({});
|
API.post.mockResolvedValue({});
|
||||||
await actions.executeCampaign({ commit }, params);
|
await actions.executeCampaign({ commit }, params);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
['setCampaignExecuted'],
|
[
|
||||||
|
'conversation/setConversationUIFlag',
|
||||||
|
{
|
||||||
|
isCreating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
['setActiveCampaign', {}],
|
['setActiveCampaign', {}],
|
||||||
|
[
|
||||||
|
'conversation/setConversationUIFlag',
|
||||||
|
{
|
||||||
|
isCreating: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('sends correct actions if execute campaign API is failed', async () => {
|
it('sends correct actions if execute campaign API is failed', async () => {
|
||||||
const params = { campaignId: 12, websiteToken: 'XDsafmADasd' };
|
const params = { campaignId: 12, websiteToken: 'XDsafmADasd' };
|
||||||
API.post.mockRejectedValue({ message: 'Authentication required' });
|
API.post.mockRejectedValue({ message: 'Authentication required' });
|
||||||
await actions.executeCampaign({ commit }, params);
|
await actions.executeCampaign({ commit }, params);
|
||||||
expect(commit.mock.calls).toEqual([['setError', true]]);
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[
|
||||||
|
'conversation/setConversationUIFlag',
|
||||||
|
{
|
||||||
|
isCreating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['setError', true],
|
||||||
|
[
|
||||||
|
'conversation/setConversationUIFlag',
|
||||||
|
{
|
||||||
|
isCreating: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -129,17 +129,4 @@ describe('#getters', () => {
|
||||||
updated_at: '2021-05-03T04:53:36.354Z',
|
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,14 +18,6 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#setHasFetched', () => {
|
|
||||||
it('set fetched flag', () => {
|
|
||||||
const state = { records: [], uiFlags: {} };
|
|
||||||
mutations.setHasFetched(state, true);
|
|
||||||
expect(state.uiFlags.hasFetched).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#setActiveCampaign', () => {
|
describe('#setActiveCampaign', () => {
|
||||||
it('set active campaign', () => {
|
it('set active campaign', () => {
|
||||||
const state = { records: [] };
|
const state = { records: [] };
|
||||||
|
@ -33,12 +25,4 @@ describe('#mutations', () => {
|
||||||
expect(state.activeCampaign).toEqual(campaigns[0]);
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR';
|
|
||||||
export const SET_REFERRER_HOST = 'SET_REFERRER_HOST';
|
|
||||||
|
|
||||||
export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES';
|
|
||||||
export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES';
|
|
||||||
export const CLEAR_CONVERSATION_ATTRIBUTES = 'CLEAR_CONVERSATION_ATTRIBUTES';
|
export const CLEAR_CONVERSATION_ATTRIBUTES = 'CLEAR_CONVERSATION_ATTRIBUTES';
|
||||||
|
export const SET_CONVERSATION_ATTRIBUTES = 'SET_CONVERSATION_ATTRIBUTES';
|
||||||
|
export const SET_WIDGET_APP_CONFIG = 'SET_WIDGET_APP_CONFIG';
|
||||||
|
export const SET_WIDGET_COLOR = 'SET_WIDGET_COLOR';
|
||||||
|
export const UPDATE_CONVERSATION_ATTRIBUTES = 'UPDATE_CONVERSATION_ATTRIBUTES';
|
||||||
|
export const TOGGLE_WIDGET_OPEN = 'TOGGLE_WIDGET_OPEN';
|
||||||
|
export const SET_REFERRER_HOST = 'SET_REFERRER_HOST';
|
||||||
|
|
28
app/javascript/widget/views/Campaigns.vue
Normal file
28
app/javascript/widget/views/Campaigns.vue
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<unread-message-list :messages="messages" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import UnreadMessageList from '../components/UnreadMessageList.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Campaigns',
|
||||||
|
components: {
|
||||||
|
UnreadMessageList,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ campaign: 'campaign/getActiveCampaign' }),
|
||||||
|
messages() {
|
||||||
|
const { sender, id: campaignId, message: content } = this.campaign;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
content,
|
||||||
|
sender,
|
||||||
|
campaignId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,113 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-1 flex-col justify-end">
|
||||||
v-if="!conversationSize && isFetchingList"
|
|
||||||
class="flex flex-1 items-center h-full bg-black-25 justify-center"
|
|
||||||
>
|
|
||||||
<spinner size="" />
|
|
||||||
</div>
|
|
||||||
<div v-else class="home" @keydown.esc="closeChat">
|
|
||||||
<div
|
|
||||||
class="header-wrap bg-white"
|
|
||||||
:class="{ expanded: !isHeaderCollapsed, collapsed: isHeaderCollapsed }"
|
|
||||||
>
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition-all delay-200 duration-300 ease"
|
|
||||||
leave-active-class="transition-all duration-200 ease-in"
|
|
||||||
enter-class="opacity-0 transform"
|
|
||||||
enter-to-class="opacity-100 transform"
|
|
||||||
leave-class="opacity-100 transform"
|
|
||||||
leave-to-class="opacity-0 transform"
|
|
||||||
>
|
|
||||||
<chat-header-expanded
|
|
||||||
v-if="!isHeaderCollapsed"
|
|
||||||
:intro-heading="channelConfig.welcomeTitle"
|
|
||||||
:intro-body="channelConfig.welcomeTagline"
|
|
||||||
:avatar-url="channelConfig.avatarUrl"
|
|
||||||
:show-popout-button="showPopoutButton"
|
|
||||||
/>
|
|
||||||
<chat-header
|
|
||||||
v-if="isHeaderCollapsed"
|
|
||||||
:title="channelConfig.websiteName"
|
|
||||||
:avatar-url="channelConfig.avatarUrl"
|
|
||||||
:show-popout-button="showPopoutButton"
|
|
||||||
:available-agents="availableAgents"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
<banner />
|
|
||||||
<div class="flex flex-1 overflow-auto">
|
<div class="flex flex-1 overflow-auto">
|
||||||
<conversation-wrap
|
<!-- Load Converstion List Components Here -->
|
||||||
v-if="currentView === 'messageView'"
|
|
||||||
:grouped-messages="groupedMessages"
|
|
||||||
/>
|
|
||||||
<pre-chat-form
|
|
||||||
v-if="currentView === 'preChatFormView'"
|
|
||||||
:options="preChatFormOptions"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="footer-wrap">
|
|
||||||
<transition
|
|
||||||
enter-active-class="transition-all delay-300 duration-300 ease"
|
|
||||||
leave-active-class="transition-all duration-200 ease-in"
|
|
||||||
enter-class="opacity-0 transform"
|
|
||||||
enter-to-class="opacity-100 transform translate-y-0"
|
|
||||||
leave-class="opacity-100 transform translate-y-0"
|
|
||||||
leave-to-class="opacity-0 transform "
|
|
||||||
>
|
|
||||||
<div v-if="currentView === 'messageView'" class="input-wrap">
|
|
||||||
<chat-footer />
|
|
||||||
</div>
|
|
||||||
<team-availability
|
|
||||||
v-if="currentView === 'cardView'"
|
|
||||||
:available-agents="availableAgents"
|
|
||||||
@start-conversation="startConversation"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
<branding></branding>
|
|
||||||
</div>
|
</div>
|
||||||
|
<team-availability
|
||||||
|
:available-agents="availableAgents"
|
||||||
|
:has-conversation="!!conversationSize"
|
||||||
|
@start-conversation="startConversation"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Branding from 'shared/components/Branding.vue';
|
|
||||||
import ChatFooter from 'widget/components/ChatFooter.vue';
|
|
||||||
import ChatHeaderExpanded from 'widget/components/ChatHeaderExpanded.vue';
|
|
||||||
import ChatHeader from 'widget/components/ChatHeader.vue';
|
|
||||||
import ConversationWrap from 'widget/components/ConversationWrap.vue';
|
|
||||||
import { IFrameHelper } from 'widget/helpers/utils';
|
|
||||||
import configMixin from '../mixins/configMixin';
|
import configMixin from '../mixins/configMixin';
|
||||||
import TeamAvailability from 'widget/components/TeamAvailability';
|
import TeamAvailability from 'widget/components/TeamAvailability';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
|
||||||
import Banner from 'widget/components/Banner.vue';
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages';
|
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import PreChatForm from '../components/PreChat/Form';
|
import routerMixin from 'widget/mixins/routerMixin';
|
||||||
import { isEmptyObject } from 'widget/helpers/utils';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
components: {
|
components: {
|
||||||
Branding,
|
|
||||||
ChatFooter,
|
|
||||||
ChatHeader,
|
|
||||||
ChatHeaderExpanded,
|
|
||||||
ConversationWrap,
|
|
||||||
PreChatForm,
|
|
||||||
Spinner,
|
|
||||||
TeamAvailability,
|
TeamAvailability,
|
||||||
Banner,
|
|
||||||
},
|
},
|
||||||
mixins: [configMixin],
|
mixins: [configMixin, routerMixin],
|
||||||
props: {
|
props: {
|
||||||
hasFetched: {
|
hasFetched: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
showPopoutButton: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isCampaignViewClicked: {
|
isCampaignViewClicked: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -122,57 +42,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
availableAgents: 'agent/availableAgents',
|
availableAgents: 'agent/availableAgents',
|
||||||
conversationAttributes: 'conversationAttributes/getConversationParams',
|
|
||||||
conversationSize: 'conversation/getConversationSize',
|
|
||||||
groupedMessages: 'conversation/getGroupedConversation',
|
|
||||||
isFetchingList: 'conversation/getIsFetchingList',
|
|
||||||
currentUser: 'contacts/getCurrentUser',
|
|
||||||
activeCampaign: 'campaign/getActiveCampaign',
|
activeCampaign: 'campaign/getActiveCampaign',
|
||||||
getCampaignHasExecuted: 'campaign/getCampaignHasExecuted',
|
conversationSize: 'conversation/getConversationSize',
|
||||||
}),
|
}),
|
||||||
currentView() {
|
|
||||||
const { email: currentUserEmail = '' } = this.currentUser;
|
|
||||||
|
|
||||||
if (this.isHeaderCollapsed) {
|
|
||||||
if (this.conversationSize) {
|
|
||||||
return 'messageView';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!this.getCampaignHasExecuted &&
|
|
||||||
((this.preChatFormEnabled &&
|
|
||||||
!isEmptyObject(this.activeCampaign) &&
|
|
||||||
this.preChatFormOptions.requireEmail) ||
|
|
||||||
this.isOnNewConversation ||
|
|
||||||
(this.preChatFormEnabled && !currentUserEmail))
|
|
||||||
) {
|
|
||||||
return 'preChatFormView';
|
|
||||||
}
|
|
||||||
return 'messageView';
|
|
||||||
}
|
|
||||||
return 'cardView';
|
|
||||||
},
|
|
||||||
isOpen() {
|
|
||||||
return this.conversationAttributes.status === 'open';
|
|
||||||
},
|
|
||||||
fileUploadSizeLimit() {
|
|
||||||
return MAXIMUM_FILE_UPLOAD_SIZE;
|
|
||||||
},
|
|
||||||
isHeaderCollapsed() {
|
|
||||||
if (
|
|
||||||
!this.hasIntroText ||
|
|
||||||
this.conversationSize ||
|
|
||||||
this.isCampaignViewClicked
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.isOnCollapsedView;
|
|
||||||
},
|
|
||||||
hasIntroText() {
|
|
||||||
return (
|
|
||||||
this.channelConfig.welcomeTitle || this.channelConfig.welcomeTagline
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
bus.$on(BUS_EVENTS.START_NEW_CONVERSATION, () => {
|
bus.$on(BUS_EVENTS.START_NEW_CONVERSATION, () => {
|
||||||
|
@ -182,73 +54,11 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
startConversation() {
|
startConversation() {
|
||||||
this.isOnCollapsedView = !this.isOnCollapsedView;
|
if (this.preChatFormEnabled && !this.conversationSize) {
|
||||||
},
|
return this.replaceRoute('prechat-form');
|
||||||
closeChat() {
|
}
|
||||||
IFrameHelper.sendMessage({ event: 'closeChat' });
|
return this.replaceRoute('messages');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import '~widget/assets/scss/variables';
|
|
||||||
@import '~widget/assets/scss/mixins';
|
|
||||||
|
|
||||||
.home {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
background: $color-background;
|
|
||||||
|
|
||||||
.header-wrap {
|
|
||||||
border-radius: $space-normal $space-normal 0 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: max-height 300ms;
|
|
||||||
z-index: 99;
|
|
||||||
@include shadow-large;
|
|
||||||
|
|
||||||
&.expanded {
|
|
||||||
max-height: 16rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
max-height: 4.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-device-width: 320px) and (max-device-width: 667px) {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-wrap {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -$space-normal;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: $space-normal;
|
|
||||||
opacity: 0.1;
|
|
||||||
background: linear-gradient(
|
|
||||||
to top,
|
|
||||||
$color-background,
|
|
||||||
rgba($color-background, 0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrap {
|
|
||||||
padding: 0 $space-two;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
27
app/javascript/widget/views/Messages.vue
Normal file
27
app/javascript/widget/views/Messages.vue
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col flex-1 overflow-hidden">
|
||||||
|
<div class="flex flex-1 overflow-auto">
|
||||||
|
<conversation-wrap :grouped-messages="groupedMessages" />
|
||||||
|
</div>
|
||||||
|
<div class="px-5">
|
||||||
|
<chat-footer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import ChatFooter from '../components/ChatFooter.vue';
|
||||||
|
import ConversationWrap from '../components/ConversationWrap.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { ChatFooter, ConversationWrap },
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
groupedMessages: 'conversation/getGroupedConversation',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('conversation/setUserLastSeen');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
49
app/javascript/widget/views/PreChatForm.vue
Normal file
49
app/javascript/widget/views/PreChatForm.vue
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-1 overflow-auto">
|
||||||
|
<pre-chat-form :options="preChatFormOptions" @submit="onSubmit" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import PreChatForm from '../components/PreChat/Form';
|
||||||
|
import configMixin from '../mixins/configMixin';
|
||||||
|
import routerMixin from '../mixins/routerMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PreChatForm,
|
||||||
|
},
|
||||||
|
mixins: [configMixin, routerMixin],
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
conversationSize: 'conversation/getConversationSize',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
conversationSize(newSize, oldSize) {
|
||||||
|
if (!oldSize && newSize > oldSize) {
|
||||||
|
this.replaceRoute('messages');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSubmit({ fullName, emailAddress, message, activeCampaignId }) {
|
||||||
|
if (activeCampaignId) {
|
||||||
|
bus.$emit('execute-campaign', activeCampaignId);
|
||||||
|
this.$store.dispatch('contacts/update', {
|
||||||
|
user: {
|
||||||
|
email: emailAddress,
|
||||||
|
name: fullName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('conversation/createConversation', {
|
||||||
|
fullName: fullName,
|
||||||
|
emailAddress: emailAddress,
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,82 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
id="app"
|
|
||||||
class="woot-widget-wrap"
|
|
||||||
:class="{
|
|
||||||
'is-mobile': isMobile,
|
|
||||||
'is-widget-right': !isLeftAligned,
|
|
||||||
'is-bubble-hidden': hideMessageBubble,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<home
|
|
||||||
v-if="showHomePage"
|
|
||||||
:has-fetched="hasFetched"
|
|
||||||
:unread-message-count="unreadMessageCount"
|
|
||||||
:show-popout-button="showPopoutButton"
|
|
||||||
:is-campaign-view-clicked="isCampaignViewClicked"
|
|
||||||
/>
|
|
||||||
<unread
|
|
||||||
v-else
|
|
||||||
:show-unread-view="showUnreadView"
|
|
||||||
:has-fetched="hasFetched"
|
|
||||||
:unread-message-count="unreadMessageCount"
|
|
||||||
:hide-message-bubble="hideMessageBubble"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Home from './Home';
|
|
||||||
import Unread from './Unread';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Router',
|
|
||||||
components: {
|
|
||||||
Home,
|
|
||||||
Unread,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
hasFetched: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isMobile: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isLeftAligned: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showUnreadView: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showCampaignView: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hideMessageBubble: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
unreadMessageCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
showPopoutButton: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isCampaignViewClicked: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
showHomePage() {
|
|
||||||
return !this.showUnreadView && !this.showCampaignView;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
20
app/javascript/widget/views/UnreadMessages.vue
Normal file
20
app/javascript/widget/views/UnreadMessages.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<unread-message-list :messages="messages" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import UnreadMessageList from '../components/UnreadMessageList.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UnreadMessages',
|
||||||
|
components: {
|
||||||
|
UnreadMessageList,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
messages: 'conversation/getUnreadTextMessages',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
Loading…
Reference in a new issue