Feature: Send images from widget
This commit is contained in:
parent
e56132c506
commit
6c4e1fdaac
16 changed files with 305 additions and 67 deletions
|
@ -7,10 +7,16 @@ const sendMessageAPI = async content => {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendAttachmentAPI = async attachment => {
|
||||||
|
const urlData = endPoints.sendAttachmnet(attachment);
|
||||||
|
const result = await API.post(urlData.url, urlData.params);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
const getConversationAPI = async ({ before }) => {
|
const getConversationAPI = async ({ before }) => {
|
||||||
const urlData = endPoints.getConversation({ before });
|
const urlData = endPoints.getConversation({ before });
|
||||||
const result = await API.get(urlData.url, { params: urlData.params });
|
const result = await API.get(urlData.url, { params: urlData.params });
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { sendMessageAPI, getConversationAPI };
|
export { sendMessageAPI, getConversationAPI, sendAttachmentAPI };
|
||||||
|
|
|
@ -9,6 +9,22 @@ const sendMessage = content => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sendAttachmnet = ({ attachment }) => {
|
||||||
|
const { refererURL = '' } = window;
|
||||||
|
const timestamp = new Date().toString();
|
||||||
|
const { file, file_type: fileType } = attachment;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('message[attachment][file]', file);
|
||||||
|
formData.append('message[attachment][file_type]', fileType);
|
||||||
|
formData.append('message[referer_url]', refererURL);
|
||||||
|
formData.append('message[timestamp]', timestamp);
|
||||||
|
return {
|
||||||
|
url: `/api/v1/widget/messages${window.location.search}`,
|
||||||
|
params: formData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const getConversation = ({ before }) => ({
|
const getConversation = ({ before }) => ({
|
||||||
url: `/api/v1/widget/messages${window.location.search}`,
|
url: `/api/v1/widget/messages${window.location.search}`,
|
||||||
params: { before },
|
params: { before },
|
||||||
|
@ -27,6 +43,7 @@ const getAvailableAgents = token => ({
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
sendAttachmnet,
|
||||||
getConversation,
|
getConversation,
|
||||||
updateContact,
|
updateContact,
|
||||||
getAvailableAgents,
|
getAvailableAgents,
|
||||||
|
|
1
app/javascript/widget/assets/images/paperclip.svg
Normal file
1
app/javascript/widget/assets/images/paperclip.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-paperclip"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
After Width: | Height: | Size: 352 B |
|
@ -17,10 +17,10 @@
|
||||||
:message-type="messageType"
|
:message-type="messageType"
|
||||||
:message="message.content"
|
:message="message.content"
|
||||||
/>
|
/>
|
||||||
<div v-else class="chat-bubble has-attachment agent">
|
<div v-if="hasImage" class="chat-bubble has-attachment agent">
|
||||||
<image-bubble
|
<image-bubble
|
||||||
v-if="message.attachment && message.attachment.file_type === 'image'"
|
|
||||||
:url="message.attachment.data_url"
|
:url="message.attachment.data_url"
|
||||||
|
:thumb="message.attachment.thumb_url"
|
||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,9 +53,14 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
hasImage() {
|
||||||
|
const { attachment = {} } = this.message;
|
||||||
|
const { file_type: fileType } = attachment;
|
||||||
|
return fileType === 'image';
|
||||||
|
},
|
||||||
showTextBubble() {
|
showTextBubble() {
|
||||||
const { message } = this;
|
const { message } = this;
|
||||||
return !!message.content && !message.attachment;
|
return !!message.content;
|
||||||
},
|
},
|
||||||
readableTime() {
|
readableTime() {
|
||||||
const { created_at: createdAt = '' } = this.message;
|
const { created_at: createdAt = '' } = this.message;
|
||||||
|
|
66
app/javascript/widget/components/ChatAttachment.vue
Executable file
66
app/javascript/widget/components/ChatAttachment.vue
Executable file
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<file-upload accept="image/*" @input-file="onFileUpload">
|
||||||
|
<span class="attachment-button ">
|
||||||
|
<i v-if="!isUploading.image"></i>
|
||||||
|
<spinner v-if="isUploading" size="small" />
|
||||||
|
</span>
|
||||||
|
</file-upload>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FileUpload from 'vue-upload-component';
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { FileUpload, Spinner },
|
||||||
|
props: {
|
||||||
|
onAttach: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { isUploading: false };
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onFileUpload(file) {
|
||||||
|
this.isUploading = true;
|
||||||
|
try {
|
||||||
|
const thumbUrl = window.URL.createObjectURL(file.file);
|
||||||
|
await this.onAttach({
|
||||||
|
file_type: file.type,
|
||||||
|
file: file.file,
|
||||||
|
thumbUrl,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Error
|
||||||
|
}
|
||||||
|
this.isUploading = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
|
||||||
|
.attachment-button {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
padding-right: $space-smaller;
|
||||||
|
display: block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
background: white center center no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='%23999a9b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-paperclip'%3E%3Cpath d='M21 11l-9 9a6 6 0 01-8-8l9-9a4 4 0 016 5L9 17a2 2 0 01-2-2l8-9' /%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,10 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<ChatInputWrap :on-send-message="onSendMessage" />
|
<ChatInputWrap
|
||||||
|
:on-send-message="handleSendMessage"
|
||||||
|
:on-send-attachment="handleSendAttachment"
|
||||||
|
/>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
|
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -16,9 +20,16 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
onSendMessage: {
|
},
|
||||||
type: Function,
|
methods: {
|
||||||
default: () => {},
|
...mapActions('conversation', ['sendMessage', 'sendAttachment']),
|
||||||
|
handleSendMessage(content) {
|
||||||
|
this.sendMessage({
|
||||||
|
content,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSendAttachment(attachment) {
|
||||||
|
this.sendAttachment({ attachment });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="chat-message--input">
|
<div class="chat-message--input">
|
||||||
|
<chat-attchment-button :on-attach="onSendAttachment" />
|
||||||
<ChatInputArea v-model="userInput" :placeholder="placeholder" />
|
<ChatInputArea v-model="userInput" :placeholder="placeholder" />
|
||||||
<ChatSendButton
|
<ChatSendButton
|
||||||
:on-click="handleButtonClick"
|
:on-click="handleButtonClick"
|
||||||
|
@ -12,11 +13,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
||||||
|
import ChatAttchmentButton from 'widget/components/ChatAttachment.vue';
|
||||||
import ChatInputArea from 'widget/components/ChatInputArea.vue';
|
import ChatInputArea from 'widget/components/ChatInputArea.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatInputWrap',
|
name: 'ChatInputWrap',
|
||||||
components: {
|
components: {
|
||||||
|
ChatAttchmentButton,
|
||||||
ChatSendButton,
|
ChatSendButton,
|
||||||
ChatInputArea,
|
ChatInputArea,
|
||||||
},
|
},
|
||||||
|
@ -30,6 +33,10 @@ export default {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
onSendAttachment: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<UserMessage
|
<UserMessage v-if="isUserMessage" :message="message" />
|
||||||
v-if="isUserMessage"
|
|
||||||
:message="message.content"
|
|
||||||
:status="message.status"
|
|
||||||
/>
|
|
||||||
<AgentMessage v-else :message="message" />
|
<AgentMessage v-else :message="message" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<a :href="url" target="_blank" class="image message-text__wrap">
|
<a :href="url" target="_blank" class="image">
|
||||||
<img :src="url" alt="Picture message" />
|
<div class="wrap">
|
||||||
<span class="time">{{ readableTime }}</span>
|
<img :src="thumb" alt="Picture message" />
|
||||||
|
<span class="time">{{ readableTime }}</span>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ['url', 'readableTime'],
|
props: ['url', 'thumb', 'readableTime'],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~widget/assets/scss/variables.scss';
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
max-width: 100%;
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
$color-black: #000;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
-180deg,
|
||||||
|
transparent 3%,
|
||||||
|
$color-black 70%
|
||||||
|
);
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
height: 20%;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0.8;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -30,21 +52,5 @@ export default {
|
||||||
right: $space-small;
|
right: $space-small;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
|
||||||
$color-black: #000;
|
|
||||||
background-image: linear-gradient(
|
|
||||||
-180deg,
|
|
||||||
transparent 3%,
|
|
||||||
$color-black 70%
|
|
||||||
);
|
|
||||||
bottom: 0;
|
|
||||||
content: '';
|
|
||||||
height: 20%;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0.8;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,25 +1,58 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-message">
|
<div class="user-message">
|
||||||
<div class="message-wrap">
|
<div class="message-wrap" :class="{ 'in-progress': isInProgress }">
|
||||||
<UserMessageBubble :message="message" :status="status" />
|
<UserMessageBubble
|
||||||
|
v-if="showTextBubble"
|
||||||
|
:message="message.content"
|
||||||
|
:status="message.status"
|
||||||
|
/>
|
||||||
|
<div v-if="hasImage" class="chat-bubble has-attachment user">
|
||||||
|
<image-bubble
|
||||||
|
:url="message.attachment.data_url"
|
||||||
|
:thumb="message.attachment.thumb_url"
|
||||||
|
:readable-time="readableTime"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import UserMessageBubble from 'widget/components/UserMessageBubble.vue';
|
import UserMessageBubble from 'widget/components/UserMessageBubble';
|
||||||
|
import ImageBubble from 'widget/components/ImageBubble';
|
||||||
|
import timeMixin from 'dashboard/mixins/time';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserMessage',
|
name: 'UserMessage',
|
||||||
components: {
|
components: {
|
||||||
UserMessageBubble,
|
UserMessageBubble,
|
||||||
|
ImageBubble,
|
||||||
},
|
},
|
||||||
|
mixins: [timeMixin],
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: String,
|
message: {
|
||||||
message: String,
|
type: Object,
|
||||||
status: {
|
default: () => {},
|
||||||
type: String,
|
},
|
||||||
default: '',
|
},
|
||||||
|
computed: {
|
||||||
|
isInProgress() {
|
||||||
|
const { status = '' } = this.message;
|
||||||
|
return status === 'in_progress';
|
||||||
|
},
|
||||||
|
hasImage() {
|
||||||
|
const { attachment = {} } = this.message;
|
||||||
|
const { file_type: fileType } = attachment;
|
||||||
|
|
||||||
|
return fileType === 'image';
|
||||||
|
},
|
||||||
|
showTextBubble() {
|
||||||
|
const { message } = this;
|
||||||
|
return !!message.content;
|
||||||
|
},
|
||||||
|
readableTime() {
|
||||||
|
const { created_at: createdAt = '' } = this.message;
|
||||||
|
return this.messageStamp(createdAt);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -51,6 +84,15 @@ export default {
|
||||||
.message-wrap {
|
.message-wrap {
|
||||||
margin-right: $space-small;
|
margin-right: $space-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.in-progress {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-attachment {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="chat-bubble user"
|
class="chat-bubble user"
|
||||||
:style="{ background: backgroundColor }"
|
:style="{ background: widgetColor }"
|
||||||
v-html="formatMessage(message)"
|
v-html="formatMessage(message)"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -12,14 +12,6 @@ import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserMessageBubble',
|
name: 'UserMessageBubble',
|
||||||
computed: {
|
|
||||||
...mapGetters({
|
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
|
||||||
}),
|
|
||||||
backgroundColor() {
|
|
||||||
return this.status !== 'in_progress' ? this.widgetColor : '#c0ccda';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mixins: [messageFormatterMixin],
|
mixins: [messageFormatterMixin],
|
||||||
props: {
|
props: {
|
||||||
message: String,
|
message: String,
|
||||||
|
@ -27,6 +19,16 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { sendMessageAPI, getConversationAPI } from 'widget/api/conversation';
|
import {
|
||||||
|
sendMessageAPI,
|
||||||
|
getConversationAPI,
|
||||||
|
sendAttachmentAPI,
|
||||||
|
} from 'widget/api/conversation';
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
import { playNotificationAudio } from 'shared/helpers/AudioNotificationHelper';
|
import { playNotificationAudio } from 'shared/helpers/AudioNotificationHelper';
|
||||||
import getUuid from '../../helpers/uuid';
|
import getUuid from '../../helpers/uuid';
|
||||||
|
@ -8,11 +12,12 @@ import DateHelper from '../../../shared/helpers/DateHelper';
|
||||||
|
|
||||||
const groupBy = require('lodash.groupby');
|
const groupBy = require('lodash.groupby');
|
||||||
|
|
||||||
export const createTemporaryMessage = content => {
|
export const createTemporaryMessage = ({ attachment, content }) => {
|
||||||
const timestamp = new Date().getTime() / 1000;
|
const timestamp = new Date().getTime() / 1000;
|
||||||
return {
|
return {
|
||||||
id: getUuid(),
|
id: getUuid(),
|
||||||
content,
|
content,
|
||||||
|
attachment,
|
||||||
status: 'in_progress',
|
status: 'in_progress',
|
||||||
created_at: timestamp,
|
created_at: timestamp,
|
||||||
message_type: MESSAGE_TYPE.INCOMING,
|
message_type: MESSAGE_TYPE.INCOMING,
|
||||||
|
@ -78,10 +83,29 @@ export const getters = {
|
||||||
export const actions = {
|
export const actions = {
|
||||||
sendMessage: async ({ commit }, params) => {
|
sendMessage: async ({ commit }, params) => {
|
||||||
const { content } = params;
|
const { content } = params;
|
||||||
commit('pushMessageToConversation', createTemporaryMessage(content));
|
commit('pushMessageToConversation', createTemporaryMessage({ content }));
|
||||||
await sendMessageAPI(content);
|
await sendMessageAPI(content);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sendAttachment: async ({ commit }, params) => {
|
||||||
|
const { attachment } = params;
|
||||||
|
const { thumbUrl } = attachment;
|
||||||
|
const attachmentBlob = {
|
||||||
|
thumb_url: thumbUrl,
|
||||||
|
data_url: thumbUrl,
|
||||||
|
file_type: 'image',
|
||||||
|
status: 'in_progress',
|
||||||
|
};
|
||||||
|
const tempMessage = createTemporaryMessage({ attachment: attachmentBlob });
|
||||||
|
commit('pushMessageToConversation', tempMessage);
|
||||||
|
try {
|
||||||
|
const { data } = await sendAttachmentAPI(params);
|
||||||
|
commit('setMessageStatus', { message: data, tempId: tempMessage.id });
|
||||||
|
} catch (error) {
|
||||||
|
// Show error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
fetchOldConversations: async ({ commit }, { before } = {}) => {
|
fetchOldConversations: async ({ commit }, { before } = {}) => {
|
||||||
try {
|
try {
|
||||||
commit('setConversationListLoading', true);
|
commit('setConversationListLoading', true);
|
||||||
|
@ -126,6 +150,19 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setMessageStatus($state, { message, tempId }) {
|
||||||
|
const { status, id } = message;
|
||||||
|
const messagesInbox = $state.conversations;
|
||||||
|
|
||||||
|
const messageInConversation = messagesInbox[tempId];
|
||||||
|
|
||||||
|
if (messageInConversation) {
|
||||||
|
Vue.delete(messagesInbox, tempId);
|
||||||
|
const newMessage = { ...messageInConversation };
|
||||||
|
Vue.set(messagesInbox, id, { ...newMessage, id, status });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setConversationListLoading($state, status) {
|
setConversationListLoading($state, status) {
|
||||||
$state.uiFlags.isFetchingList = status;
|
$state.uiFlags.isFetchingList = status;
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,4 +42,30 @@ describe('#actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#sendAttachment', () => {
|
||||||
|
it('sends correct mutations', () => {
|
||||||
|
const mockDate = new Date(1466424490000);
|
||||||
|
getUuid.mockImplementationOnce(() => '1111');
|
||||||
|
const spy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||||
|
const thumbUrl = '';
|
||||||
|
const attachment = { thumbUrl };
|
||||||
|
|
||||||
|
actions.sendAttachment({ commit }, { attachment });
|
||||||
|
spy.mockRestore();
|
||||||
|
expect(commit).toBeCalledWith('pushMessageToConversation', {
|
||||||
|
id: '1111',
|
||||||
|
content: undefined,
|
||||||
|
status: 'in_progress',
|
||||||
|
created_at: 1466424490,
|
||||||
|
message_type: 0,
|
||||||
|
attachment: {
|
||||||
|
thumb_url: '',
|
||||||
|
data_url: '',
|
||||||
|
file_type: 'image',
|
||||||
|
status: 'in_progress',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -92,4 +92,29 @@ describe('#mutations', () => {
|
||||||
expect(state.uiFlags.allMessagesLoaded).toEqual(false);
|
expect(state.uiFlags.allMessagesLoaded).toEqual(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#setMessageStatus', () => {
|
||||||
|
it('Updates status of loading messages if payload is not empty', () => {
|
||||||
|
const state = {
|
||||||
|
conversations: {
|
||||||
|
rand_id_123: {
|
||||||
|
content: '',
|
||||||
|
id: 'rand_id_123',
|
||||||
|
message_type: 0,
|
||||||
|
status: 'in_progress',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const message = {
|
||||||
|
id: '1',
|
||||||
|
content: '',
|
||||||
|
status: 'sent',
|
||||||
|
};
|
||||||
|
mutations.setMessageStatus(state, { message, tempId: 'rand_id_123' });
|
||||||
|
|
||||||
|
expect(state.conversations).toEqual({
|
||||||
|
1: { id: '1', content: '', message_type: 0, status: 'sent' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,7 +30,7 @@ describe('#findUndeliveredMessage', () => {
|
||||||
|
|
||||||
describe('#createTemporaryMessage', () => {
|
describe('#createTemporaryMessage', () => {
|
||||||
it('returns message object', () => {
|
it('returns message object', () => {
|
||||||
const message = createTemporaryMessage('hello');
|
const message = createTemporaryMessage({ content: 'hello' });
|
||||||
expect(message.content).toBe('hello');
|
expect(message.content).toBe('hello');
|
||||||
expect(message.status).toBe('in_progress');
|
expect(message.status).toBe('in_progress');
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<ConversationWrap :grouped-messages="groupedMessages" />
|
<ConversationWrap :grouped-messages="groupedMessages" />
|
||||||
<div class="footer-wrap">
|
<div class="footer-wrap">
|
||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
<ChatFooter :on-send-message="handleSendMessage" />
|
<ChatFooter />
|
||||||
</div>
|
</div>
|
||||||
<branding></branding>
|
<branding></branding>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import Branding from 'widget/components/Branding.vue';
|
import Branding from 'widget/components/Branding.vue';
|
||||||
import ChatFooter from 'widget/components/ChatFooter.vue';
|
import ChatFooter from 'widget/components/ChatFooter.vue';
|
||||||
|
@ -52,15 +52,6 @@ export default {
|
||||||
return this.availableAgents.length > 0 && this.conversationSize < 1;
|
return this.availableAgents.length > 0 && this.conversationSize < 1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
|
||||||
...mapActions('conversation', ['sendMessage']),
|
|
||||||
handleSendMessage(content) {
|
|
||||||
this.sendMessage({
|
|
||||||
content,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue