feat: Add a pre-chat form on widget (#1769)
This commit is contained in:
@@ -2,12 +2,7 @@
|
||||
<router
|
||||
:show-unread-view="showUnreadView"
|
||||
:is-mobile="isMobile"
|
||||
:grouped-messages="groupedMessages"
|
||||
:unread-messages="unreadMessages"
|
||||
:conversation-size="conversationSize"
|
||||
:available-agents="availableAgents"
|
||||
:has-fetched="hasFetched"
|
||||
:conversation-attributes="conversationAttributes"
|
||||
:unread-message-count="unreadMessageCount"
|
||||
:is-left-aligned="isLeftAligned"
|
||||
:hide-message-bubble="hideMessageBubble"
|
||||
@@ -40,12 +35,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
groupedMessages: 'conversation/getGroupedConversation',
|
||||
unreadMessages: 'conversation/getUnreadTextMessages',
|
||||
conversationSize: 'conversation/getConversationSize',
|
||||
availableAgents: 'agent/availableAgents',
|
||||
hasFetched: 'agent/getHasFetched',
|
||||
conversationAttributes: 'conversationAttributes/getConversationParams',
|
||||
unreadMessageCount: 'conversation/getUnreadMessageCount',
|
||||
}),
|
||||
isLeftAligned() {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import endPoints from 'widget/api/endPoints';
|
||||
import { API } from 'widget/helpers/axios';
|
||||
|
||||
const createConversationAPI = async content => {
|
||||
const urlData = endPoints.createConversation(content);
|
||||
const result = await API.post(urlData.url, urlData.params);
|
||||
return result;
|
||||
};
|
||||
|
||||
const sendMessageAPI = async content => {
|
||||
const urlData = endPoints.sendMessage(content);
|
||||
const result = await API.post(urlData.url, urlData.params);
|
||||
@@ -38,6 +44,7 @@ const setUserLastSeenAt = async ({ lastSeen }) => {
|
||||
};
|
||||
|
||||
export {
|
||||
createConversationAPI,
|
||||
sendMessageAPI,
|
||||
getConversationAPI,
|
||||
getMessagesAPI,
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
import { buildSearchParamsWithLocale } from '../helpers/urlParamsHelper';
|
||||
|
||||
const createConversation = params => {
|
||||
const referrerURL = window.referrerURL || '';
|
||||
const search = buildSearchParamsWithLocale(window.location.search);
|
||||
return {
|
||||
url: `/api/v1/widget/conversations${search}`,
|
||||
params: {
|
||||
contact: {
|
||||
name: params.fullName,
|
||||
email: params.emailAddress,
|
||||
},
|
||||
message: {
|
||||
content: params.message,
|
||||
timestamp: new Date().toString(),
|
||||
referer_url: referrerURL,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const sendMessage = content => {
|
||||
const referrerURL = window.referrerURL || '';
|
||||
const search = buildSearchParamsWithLocale(window.location.search);
|
||||
@@ -47,6 +66,7 @@ const getAvailableAgents = token => ({
|
||||
});
|
||||
|
||||
export default {
|
||||
createConversation,
|
||||
sendMessage,
|
||||
sendAttachment,
|
||||
getConversation,
|
||||
|
||||
@@ -75,8 +75,6 @@ export default {
|
||||
padding: $space-two $space-medium;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
@include shadow-large;
|
||||
|
||||
.header-branding {
|
||||
display: flex;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<header class="header-expanded py-8 px-6 bg-white relative box-border w-full">
|
||||
<header class="header-expanded bg-white py-8 px-6 relative box-border w-full">
|
||||
<div class="flex justify-between items-start">
|
||||
<img v-if="avatarUrl" class="logo" :src="avatarUrl" />
|
||||
<header-actions :show-popout-button="showPopoutButton" />
|
||||
@@ -50,8 +50,6 @@ export default {
|
||||
@import '~widget/assets/scss/mixins.scss';
|
||||
|
||||
.header-expanded {
|
||||
@include shadow-large;
|
||||
|
||||
.logo {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
|
||||
59
app/javascript/widget/components/Form/Input.vue
Normal file
59
app/javascript/widget/components/Form/Input.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<label class="block">
|
||||
<div
|
||||
v-if="label"
|
||||
class="mb-2 text-xs font-medium"
|
||||
:class="{
|
||||
'text-black-800': !error,
|
||||
'text-red-400': error,
|
||||
}"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
<input
|
||||
:type="type"
|
||||
class="border rounded w-full py-2 px-3 text-slate-700 leading-tight outline-none"
|
||||
:class="{
|
||||
'border-black-200 hover:border-black-300 focus:border-black-300': !error,
|
||||
'border-red-200 hover:border-red-300 focus:border-red-300': error,
|
||||
}"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
/>
|
||||
<div v-if="error" class="text-red-400 mt-2 text-xs font-medium">
|
||||
{{ error }}
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(event) {
|
||||
this.$emit('input', event.target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
63
app/javascript/widget/components/Form/TextArea.vue
Normal file
63
app/javascript/widget/components/Form/TextArea.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<label class="block">
|
||||
<div
|
||||
v-if="label"
|
||||
class="mb-2 text-xs font-medium"
|
||||
:class="{
|
||||
'text-black-800': !error,
|
||||
'text-red-400': error,
|
||||
}"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
<textarea
|
||||
class="resize-none border rounded w-full py-2 px-3 text-slate-700 leading-tight outline-none"
|
||||
:class="{
|
||||
'border-black-200 hover:border-black-300 focus:border-black-300': !error,
|
||||
'border-red-200 hover:border-red-300 focus:border-red-300': error,
|
||||
}"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
/>
|
||||
<div v-if="error" class="text-red-400 mt-2 text-xs font-medium">
|
||||
{{ error }}
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(event) {
|
||||
this.$emit('input', event.target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
textarea {
|
||||
min-height: 8rem;
|
||||
}
|
||||
</style>
|
||||
117
app/javascript/widget/components/PreChat/Form.vue
Normal file
117
app/javascript/widget/components/PreChat/Form.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<form
|
||||
class="flex flex-1 flex-col p-6 overflow-y-scroll"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<div v-if="options.preChatMessage" class="text-black-800 text-sm leading-5">
|
||||
{{ options.preChatMessage }}
|
||||
</div>
|
||||
<form-input
|
||||
v-if="options.requireEmail"
|
||||
v-model="fullName"
|
||||
class="mt-5"
|
||||
:label="$t('PRE_CHAT_FORM.FIELDS.FULL_NAME.LABEL')"
|
||||
:placeholder="$t('PRE_CHAT_FORM.FIELDS.FULL_NAME.PLACEHOLDER')"
|
||||
type="text"
|
||||
:error="
|
||||
$v.fullName.$error ? $t('PRE_CHAT_FORM.FIELDS.FULL_NAME.ERROR') : ''
|
||||
"
|
||||
/>
|
||||
<form-input
|
||||
v-if="options.requireEmail"
|
||||
v-model="emailAddress"
|
||||
class="mt-5"
|
||||
:label="$t('PRE_CHAT_FORM.FIELDS.EMAIL_ADDRESS.LABEL')"
|
||||
:placeholder="$t('PRE_CHAT_FORM.FIELDS.EMAIL_ADDRESS.PLACEHOLDER')"
|
||||
type="email"
|
||||
:error="
|
||||
$v.emailAddress.$error
|
||||
? $t('PRE_CHAT_FORM.FIELDS.EMAIL_ADDRESS.ERROR')
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
<form-text-area
|
||||
v-model="message"
|
||||
class="my-5"
|
||||
:label="$t('PRE_CHAT_FORM.FIELDS.MESSAGE.LABEL')"
|
||||
:placeholder="$t('PRE_CHAT_FORM.FIELDS.MESSAGE.PLACEHOLDER')"
|
||||
:error="$v.message.$error ? $t('PRE_CHAT_FORM.FIELDS.MESSAGE.ERROR') : ''"
|
||||
/>
|
||||
<woot-button
|
||||
class="font-medium"
|
||||
block
|
||||
:bg-color="widgetColor"
|
||||
:text-color="textColor"
|
||||
:disabled="isCreating"
|
||||
>
|
||||
<spinner v-if="isCreating" class="p-0" />
|
||||
{{ $t('START_CONVERSATION') }}
|
||||
</woot-button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WootButton from 'shared/components/Button';
|
||||
import FormInput from '../Form/Input';
|
||||
import FormTextArea from '../Form/TextArea';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getContrastingTextColor } from 'shared/helpers/ColorHelper';
|
||||
import { required, minLength, email } from 'vuelidate/lib/validators';
|
||||
export default {
|
||||
components: {
|
||||
FormInput,
|
||||
FormTextArea,
|
||||
WootButton,
|
||||
Spinner,
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
fullName: {
|
||||
required,
|
||||
},
|
||||
emailAddress: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
message: {
|
||||
required,
|
||||
minLength: minLength(10),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullName: '',
|
||||
emailAddress: '',
|
||||
message: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
isCreating: 'conversation/getIsCreating',
|
||||
}),
|
||||
textColor() {
|
||||
return getContrastingTextColor(this.widgetColor);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('conversation/createConversation', {
|
||||
fullName: this.fullName,
|
||||
emailAddress: this.emailAddress,
|
||||
message: this.message,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -29,5 +29,24 @@
|
||||
"EMAIL_PLACEHOLDER": "Please enter your email",
|
||||
"CHAT_PLACEHOLDER": "Type your message",
|
||||
"TODAY": "Today",
|
||||
"YESTERDAY": "Yesterday"
|
||||
"YESTERDAY": "Yesterday",
|
||||
"PRE_CHAT_FORM": {
|
||||
"FIELDS": {
|
||||
"FULL_NAME": {
|
||||
"LABEL": "Full Name",
|
||||
"PLACEHOLDER": "Please enter your full name",
|
||||
"ERROR": "Full Name is required"
|
||||
},
|
||||
"EMAIL_ADDRESS": {
|
||||
"LABEL": "Email Address",
|
||||
"PLACEHOLDER": "Please enter your email address",
|
||||
"ERROR": "Invalid email address"
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Message",
|
||||
"PLACEHOLDER": "Please enter your message",
|
||||
"ERROR": "Message too short"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ export default {
|
||||
replyTime() {
|
||||
return window.chatwootWebChannel.replyTime;
|
||||
},
|
||||
preChatFormEnabled() {
|
||||
return window.chatwootWebChannel.preChatFormEnabled;
|
||||
},
|
||||
preChatFormOptions() {
|
||||
const options = window.chatwootWebChannel.preChatFormOptions || {};
|
||||
return {
|
||||
requireEmail: options.require_email,
|
||||
preChatMessage: options.pre_chat_message,
|
||||
};
|
||||
},
|
||||
replyTimeStatus() {
|
||||
switch (this.replyTime) {
|
||||
case 'in_a_few_minutes':
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
import {
|
||||
createConversationAPI,
|
||||
sendMessageAPI,
|
||||
getMessagesAPI,
|
||||
sendAttachmentAPI,
|
||||
toggleTyping,
|
||||
setUserLastSeenAt,
|
||||
} from 'widget/api/conversation';
|
||||
import { refreshActionCableConnector } from '../../../helpers/actionCable';
|
||||
|
||||
import { createTemporaryMessage, onNewMessageCreated } from './helpers';
|
||||
|
||||
export const actions = {
|
||||
createConversation: async ({ commit }, params) => {
|
||||
commit('setConversationUIFlag', { isCreating: true });
|
||||
try {
|
||||
const { data } = await createConversationAPI(params);
|
||||
const {
|
||||
contact: { pubsub_token: pubsubToken },
|
||||
messages,
|
||||
} = data;
|
||||
const [message = {}] = messages;
|
||||
commit('pushMessageToConversation', message);
|
||||
refreshActionCableConnector(pubsubToken);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit('setConversationUIFlag', { isCreating: false });
|
||||
}
|
||||
},
|
||||
sendMessage: async ({ commit }, params) => {
|
||||
const { content } = params;
|
||||
commit('pushMessageToConversation', createTemporaryMessage({ content }));
|
||||
|
||||
@@ -5,6 +5,7 @@ import { formatUnixDate } from 'shared/helpers/DateHelper';
|
||||
|
||||
export const getters = {
|
||||
getAllMessagesLoaded: _state => _state.uiFlags.allMessagesLoaded,
|
||||
getIsCreating: _state => _state.uiFlags.isCreating,
|
||||
getIsAgentTyping: _state => _state.uiFlags.isAgentTyping,
|
||||
getConversation: _state => _state.conversations,
|
||||
getConversationSize: _state => Object.keys(_state.conversations).length,
|
||||
|
||||
@@ -11,6 +11,7 @@ const state = {
|
||||
allMessagesLoaded: false,
|
||||
isFetchingList: false,
|
||||
isAgentTyping: false,
|
||||
isCreating: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ export const mutations = {
|
||||
}
|
||||
},
|
||||
|
||||
setConversationUIFlag($state, uiFlags) {
|
||||
$state.uiFlags = {
|
||||
...$state.uiFlags,
|
||||
...uiFlags,
|
||||
};
|
||||
},
|
||||
|
||||
setConversationListLoading($state, status) {
|
||||
$state.uiFlags.isFetchingList = status;
|
||||
},
|
||||
|
||||
@@ -28,6 +28,43 @@ describe('#actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createConversation', () => {
|
||||
it('sends correct mutations', async () => {
|
||||
API.post.mockResolvedValue({
|
||||
data: {
|
||||
contact: { name: 'contact-name' },
|
||||
messages: [{ id: 1, content: 'This is a test message' }],
|
||||
},
|
||||
});
|
||||
let windowSpy = jest.spyOn(window, 'window', 'get');
|
||||
windowSpy.mockImplementation(() => ({
|
||||
WOOT_WIDGET: {
|
||||
$root: {
|
||||
$i18n: {
|
||||
locale: 'el',
|
||||
},
|
||||
},
|
||||
},
|
||||
location: {
|
||||
search: '?param=1',
|
||||
},
|
||||
}));
|
||||
await actions.createConversation(
|
||||
{ commit },
|
||||
{ contact: {}, message: 'This is a test message' }
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['setConversationUIFlag', { isCreating: true }],
|
||||
[
|
||||
'pushMessageToConversation',
|
||||
{ id: 1, content: 'This is a test message' },
|
||||
],
|
||||
['setConversationUIFlag', { isCreating: false }],
|
||||
]);
|
||||
windowSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateMessage', () => {
|
||||
it('sends correct mutations', () => {
|
||||
actions.updateMessage({ commit }, { id: 1 });
|
||||
|
||||
@@ -16,6 +16,11 @@ describe('#getters', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('getIsCreating', () => {
|
||||
const state = { uiFlags: { isCreating: true } };
|
||||
expect(getters.getIsCreating(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it('getConversationSize', () => {
|
||||
const state = {
|
||||
conversations: {
|
||||
|
||||
@@ -73,6 +73,17 @@ describe('#mutations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setConversationUIFlag', () => {
|
||||
it('set uiFlags correctly', () => {
|
||||
const state = { uiFlags: { isFetchingList: false } };
|
||||
mutations.setConversationUIFlag(state, { isCreating: true });
|
||||
expect(state.uiFlags).toEqual({
|
||||
isFetchingList: false,
|
||||
isCreating: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setMessagesInConversation', () => {
|
||||
it('sets allMessagesLoaded flag if payload is empty', () => {
|
||||
const state = { uiFlags: { allMessagesLoaded: false } };
|
||||
|
||||
@@ -3,27 +3,30 @@
|
||||
v-if="!conversationSize && isFetchingList"
|
||||
class="flex flex-1 items-center h-full bg-black-25 justify-center"
|
||||
>
|
||||
<spinner size=""></spinner>
|
||||
<spinner size="" />
|
||||
</div>
|
||||
<div v-else class="home">
|
||||
<div class="header-wrap">
|
||||
<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 -translate-y-32"
|
||||
enter-to-class="opacity-100 transform translate-y-0"
|
||||
leave-class="opacity-100 transform translate-y-0"
|
||||
leave-to-class="opacity-0 transform -translate-y-32"
|
||||
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="!isOnMessageView"
|
||||
:intro-heading="introHeading"
|
||||
:intro-body="introBody"
|
||||
v-if="!isHeaderCollapsed"
|
||||
:intro-heading="channelConfig.welcomeTitle"
|
||||
:intro-body="channelConfig.welcomeTagline"
|
||||
:avatar-url="channelConfig.avatarUrl"
|
||||
:show-popout-button="showPopoutButton"
|
||||
/>
|
||||
<chat-header
|
||||
v-if="isOnMessageView"
|
||||
v-if="isHeaderCollapsed"
|
||||
:title="channelConfig.websiteName"
|
||||
:avatar-url="channelConfig.avatarUrl"
|
||||
:show-popout-button="showPopoutButton"
|
||||
@@ -31,21 +34,33 @@
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
<conversation-wrap :grouped-messages="groupedMessages" />
|
||||
<div class="flex flex-1 overflow-scroll">
|
||||
<conversation-wrap
|
||||
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 translate-y-32"
|
||||
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 translate-y-32 "
|
||||
leave-to-class="opacity-0 transform "
|
||||
>
|
||||
<div v-if="showInputTextArea && isOnMessageView" class="input-wrap">
|
||||
<div
|
||||
v-if="showInputTextArea && currentView === 'messageView'"
|
||||
class="input-wrap"
|
||||
>
|
||||
<chat-footer />
|
||||
</div>
|
||||
<team-availability
|
||||
v-if="!isOnMessageView"
|
||||
v-if="currentView === 'cardView'"
|
||||
:available-agents="availableAgents"
|
||||
@start-conversation="startConversation"
|
||||
/>
|
||||
@@ -65,7 +80,7 @@ import configMixin from '../mixins/configMixin';
|
||||
import TeamAvailability from 'widget/components/TeamAvailability';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import PreChatForm from '../components/PreChat/Form';
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
@@ -74,45 +89,44 @@ export default {
|
||||
ChatHeader,
|
||||
ChatHeaderExpanded,
|
||||
ConversationWrap,
|
||||
PreChatForm,
|
||||
Spinner,
|
||||
TeamAvailability,
|
||||
},
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
groupedMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
conversationSize: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
hasFetched: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
conversationAttributes: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showPopoutButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMessageView: false,
|
||||
};
|
||||
return { isOnCollapsedView: false };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
availableAgents: 'agent/availableAgents',
|
||||
conversationAttributes: 'conversationAttributes/getConversationParams',
|
||||
conversationSize: 'conversation/getConversationSize',
|
||||
groupedMessages: 'conversation/getGroupedConversation',
|
||||
isFetchingList: 'conversation/getIsFetchingList',
|
||||
}),
|
||||
currentView() {
|
||||
if (this.isHeaderCollapsed) {
|
||||
if (this.conversationSize) {
|
||||
return 'messageView';
|
||||
}
|
||||
if (this.preChatFormEnabled) {
|
||||
return 'preChatFormView';
|
||||
}
|
||||
return 'messageView';
|
||||
}
|
||||
return 'cardView';
|
||||
},
|
||||
isOpen() {
|
||||
return this.conversationAttributes.status === 'open';
|
||||
},
|
||||
@@ -125,31 +139,22 @@ export default {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isOnMessageView() {
|
||||
if (this.hideWelcomeHeader) {
|
||||
isHeaderCollapsed() {
|
||||
if (!this.hasIntroText || this.conversationSize) {
|
||||
return true;
|
||||
}
|
||||
if (this.conversationSize === 0) {
|
||||
return this.showMessageView;
|
||||
}
|
||||
return true;
|
||||
|
||||
return this.isOnCollapsedView;
|
||||
},
|
||||
isHeaderExpanded() {
|
||||
return this.conversationSize === 0;
|
||||
},
|
||||
introHeading() {
|
||||
return this.channelConfig.welcomeTitle;
|
||||
},
|
||||
introBody() {
|
||||
return this.channelConfig.welcomeTagline;
|
||||
},
|
||||
hideWelcomeHeader() {
|
||||
return !(this.introHeading || this.introBody);
|
||||
hasIntroText() {
|
||||
return (
|
||||
this.channelConfig.welcomeTitle || this.channelConfig.welcomeTagline
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
startConversation() {
|
||||
this.showMessageView = !this.showMessageView;
|
||||
this.isOnCollapsedView = !this.isOnCollapsedView;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -157,6 +162,7 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables';
|
||||
@import '~widget/assets/scss/mixins';
|
||||
|
||||
.home {
|
||||
width: 100%;
|
||||
@@ -168,9 +174,19 @@ export default {
|
||||
background: $color-background;
|
||||
|
||||
.header-wrap {
|
||||
border-radius: $space-normal $space-normal 0 0;
|
||||
flex-shrink: 0;
|
||||
border-radius: $space-normal $space-normal $space-small $space-small;
|
||||
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;
|
||||
|
||||
@@ -10,21 +10,13 @@
|
||||
>
|
||||
<home
|
||||
v-if="!showUnreadView"
|
||||
:grouped-messages="groupedMessages"
|
||||
:conversation-size="conversationSize"
|
||||
:available-agents="availableAgents"
|
||||
:has-fetched="hasFetched"
|
||||
:conversation-attributes="conversationAttributes"
|
||||
:unread-message-count="unreadMessageCount"
|
||||
:show-popout-button="showPopoutButton"
|
||||
/>
|
||||
<unread
|
||||
v-else
|
||||
:unread-messages="unreadMessages"
|
||||
:conversation-size="conversationSize"
|
||||
:available-agents="availableAgents"
|
||||
:has-fetched="hasFetched"
|
||||
:conversation-attributes="conversationAttributes"
|
||||
:unread-message-count="unreadMessageCount"
|
||||
:hide-message-bubble="hideMessageBubble"
|
||||
/>
|
||||
@@ -42,22 +34,6 @@ export default {
|
||||
Unread,
|
||||
},
|
||||
props: {
|
||||
groupedMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
unreadMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
conversationSize: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
hasFetched: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -78,10 +54,6 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
conversationAttributes: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
unreadMessageCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
import AgentBubble from 'widget/components/AgentMessageBubble.vue';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'Unread',
|
||||
@@ -43,26 +44,10 @@ export default {
|
||||
},
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
unreadMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
conversationSize: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
hasFetched: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
conversationAttributes: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
unreadMessageCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
@@ -73,6 +58,9 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
unreadMessages: 'conversation/getUnreadTextMessages',
|
||||
}),
|
||||
showCloseButton() {
|
||||
return this.unreadMessageCount && this.hideMessageBubble;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user