Feature: As an end-user, I should be able to see the list of agents in the widget. (#461)
Co-authored-by: Pranav Raj S <pranavrajs@gmail.com>
This commit is contained in:
parent
33e0bd434b
commit
83b0bb4062
20 changed files with 406 additions and 34 deletions
|
@ -4,13 +4,13 @@
|
||||||
v-if="!imgError && Boolean(src)"
|
v-if="!imgError && Boolean(src)"
|
||||||
id="image"
|
id="image"
|
||||||
:src="src"
|
:src="src"
|
||||||
class="user-thumbnail"
|
:class="thumbnailClass"
|
||||||
@error="onImgError()"
|
@error="onImgError()"
|
||||||
/>
|
/>
|
||||||
<Avatar
|
<Avatar
|
||||||
v-else
|
v-else
|
||||||
:username="username"
|
:username="username"
|
||||||
class="user-thumbnail"
|
:class="thumbnailClass"
|
||||||
background-color="#1f93ff"
|
background-color="#1f93ff"
|
||||||
color="white"
|
color="white"
|
||||||
:size="avatarSize"
|
:size="avatarSize"
|
||||||
|
@ -71,6 +71,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
hasBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -89,6 +93,10 @@ export default {
|
||||||
const statusSize = `${this.avatarSize / 4}px`;
|
const statusSize = `${this.avatarSize / 4}px`;
|
||||||
return { width: statusSize, height: statusSize };
|
return { width: statusSize, height: statusSize };
|
||||||
},
|
},
|
||||||
|
thumbnailClass() {
|
||||||
|
const classname = this.hasBorder ? 'border' : '';
|
||||||
|
return `user-thumbnail ${classname}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onImgError() {
|
onImgError() {
|
||||||
|
@ -111,6 +119,11 @@ export default {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.border {
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.source-badge {
|
.source-badge {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
mounted() {
|
mounted() {
|
||||||
|
const { website_token: websiteToken = '' } = window.chatwootWebChannel;
|
||||||
if (IFrameHelper.isIFrame()) {
|
if (IFrameHelper.isIFrame()) {
|
||||||
IFrameHelper.sendMessage({
|
IFrameHelper.sendMessage({
|
||||||
event: 'loaded',
|
event: 'loaded',
|
||||||
|
@ -25,15 +26,16 @@ export default {
|
||||||
this.setWidgetColor(window.chatwootWebChannel);
|
this.setWidgetColor(window.chatwootWebChannel);
|
||||||
|
|
||||||
window.addEventListener('message', e => {
|
window.addEventListener('message', e => {
|
||||||
if (
|
const wootPrefix = 'chatwoot-widget:';
|
||||||
typeof e.data !== 'string' ||
|
const isDataNotString = typeof e.data !== 'string';
|
||||||
e.data.indexOf('chatwoot-widget:') !== 0
|
const isNotFromWoot = isDataNotString || e.data.indexOf(wootPrefix) !== 0;
|
||||||
) {
|
|
||||||
return;
|
if (isNotFromWoot) return;
|
||||||
}
|
|
||||||
const message = JSON.parse(e.data.replace('chatwoot-widget:', ''));
|
const message = JSON.parse(e.data.replace(wootPrefix, ''));
|
||||||
if (message.event === 'config-set') {
|
if (message.event === 'config-set') {
|
||||||
this.fetchOldConversations();
|
this.fetchOldConversations();
|
||||||
|
this.fetchAvailableAgents(websiteToken);
|
||||||
} else if (message.event === 'widget-visible') {
|
} else if (message.event === 'widget-visible') {
|
||||||
this.scrollConversationToBottom();
|
this.scrollConversationToBottom();
|
||||||
} else if (message.event === 'set-current-url') {
|
} else if (message.event === 'set-current-url') {
|
||||||
|
@ -44,6 +46,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('appConfig', ['setWidgetColor']),
|
...mapActions('appConfig', ['setWidgetColor']),
|
||||||
...mapActions('conversation', ['fetchOldConversations']),
|
...mapActions('conversation', ['fetchOldConversations']),
|
||||||
|
...mapActions('agent', ['fetchAvailableAgents']),
|
||||||
scrollConversationToBottom() {
|
scrollConversationToBottom() {
|
||||||
const container = this.$el.querySelector('.conversation-wrap');
|
const container = this.$el.querySelector('.conversation-wrap');
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
|
|
8
app/javascript/widget/api/agent.js
Normal file
8
app/javascript/widget/api/agent.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import endPoints from 'widget/api/endPoints';
|
||||||
|
import { API } from 'widget/helpers/axios';
|
||||||
|
|
||||||
|
export const getAvailableAgents = async websiteToken => {
|
||||||
|
const urlData = endPoints.getAvailableAgents(websiteToken);
|
||||||
|
const result = await API.get(urlData.url, { params: urlData.params });
|
||||||
|
return result;
|
||||||
|
};
|
|
@ -18,8 +18,16 @@ const updateContact = id => ({
|
||||||
url: `/api/v1/widget/messages/${id}${window.location.search}`,
|
url: `/api/v1/widget/messages/${id}${window.location.search}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getAvailableAgents = token => ({
|
||||||
|
url: '/api/v1/widget/inbox_members',
|
||||||
|
params: {
|
||||||
|
website_token: token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
getConversation,
|
getConversation,
|
||||||
updateContact,
|
updateContact,
|
||||||
|
getAvailableAgents,
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,14 +36,14 @@ $color-shadow-outline: rgba(66, 153, 225, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin shadow {
|
@mixin shadow {
|
||||||
box-shadow: 0 1px 10px -4 $color-shadow-medium,
|
box-shadow: 0 1px 10px 4px $color-shadow-medium,
|
||||||
0 1px 5px 2px $color-shadow-light;
|
0 1px 5px 2px $color-shadow-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@mixin shadow-medium {
|
@mixin shadow-medium {
|
||||||
box-shadow: 0 4px 6px -8px $color-shadow-medium,
|
box-shadow: 0 4px 24px 8px $color-shadow-medium,
|
||||||
0 2px 4px -4px $color-shadow-light;
|
0 2px 16px 4px $color-shadow-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
79
app/javascript/widget/components/AvailableAgents.vue
Normal file
79
app/javascript/widget/components/AvailableAgents.vue
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div class="available-agents">
|
||||||
|
<div class="toast-bg">
|
||||||
|
<div class="avatars-wrap">
|
||||||
|
<GroupedAvatars :users="users" />
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import GroupedAvatars from 'widget/components/GroupedAvatars.vue';
|
||||||
|
import { getAvailableAgentsText } from 'widget/helpers/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AvailableAgents',
|
||||||
|
components: { GroupedAvatars },
|
||||||
|
props: {
|
||||||
|
agents: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
onClose: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
users() {
|
||||||
|
return this.agents.map(agent => ({
|
||||||
|
id: agent.id,
|
||||||
|
avatar: agent.avatar_url,
|
||||||
|
name: agent.name,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return getAvailableAgentsText(this.agents);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
|
.available-agents {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
margin: $space-normal $space-medium;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.toast-bg {
|
||||||
|
border-radius: $space-large;
|
||||||
|
background: $color-body;
|
||||||
|
@include shadow-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: $font-size-default;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
color: $color-white;
|
||||||
|
padding: $space-one $space-normal $space-one $space-small;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatars-wrap {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: $space-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -43,14 +43,10 @@ export default {
|
||||||
.header-collapsed {
|
.header-collapsed {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: $color-white;
|
|
||||||
padding: $space-two $space-medium;
|
padding: $space-two $space-medium;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
@include shadow-large;
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: $font-size-large;
|
font-size: $font-size-large;
|
||||||
|
|
|
@ -44,16 +44,9 @@ export default {
|
||||||
@import '~widget/assets/scss/mixins.scss';
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
.header-expanded {
|
.header-expanded {
|
||||||
background: $color-white;
|
|
||||||
padding: $space-larger $space-medium $space-large;
|
padding: $space-larger $space-medium $space-large;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: $space-normal;
|
|
||||||
@include shadow-large;
|
|
||||||
|
|
||||||
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
|
@ -71,7 +64,7 @@ export default {
|
||||||
.body {
|
.body {
|
||||||
color: $color-body;
|
color: $color-body;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
line-height: 1.6;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
47
app/javascript/widget/components/GroupedAvatars.vue
Normal file
47
app/javascript/widget/components/GroupedAvatars.vue
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<div class="avatars">
|
||||||
|
<span v-for="user in users" :key="user.id" class="avatar">
|
||||||
|
<Thumbnail
|
||||||
|
size="24px"
|
||||||
|
:username="user.name"
|
||||||
|
status="online"
|
||||||
|
:src="user.avatar"
|
||||||
|
has-border
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GroupedAvatars',
|
||||||
|
components: { Thumbnail },
|
||||||
|
props: {
|
||||||
|
users: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
|
.avatars {
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: $space-one;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin-left: -$space-slab;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
width: $space-medium;
|
||||||
|
height: $space-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
26
app/javascript/widget/helpers/specs/utils.spec.js
Normal file
26
app/javascript/widget/helpers/specs/utils.spec.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { getAvailableAgentsText } from '../utils';
|
||||||
|
|
||||||
|
describe('#getAvailableAgentsText', () => {
|
||||||
|
it('returns the correct text is there is only one online agent', () => {
|
||||||
|
expect(getAvailableAgentsText([{ name: 'Pranav' }])).toEqual(
|
||||||
|
'Pranav is available'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the correct text is there are two online agents', () => {
|
||||||
|
expect(
|
||||||
|
getAvailableAgentsText([{ name: 'Pranav' }, { name: 'Nithin' }])
|
||||||
|
).toEqual('Pranav and Nithin is available');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the correct text is there are more than two online agents', () => {
|
||||||
|
expect(
|
||||||
|
getAvailableAgentsText([
|
||||||
|
{ name: 'Pranav' },
|
||||||
|
{ name: 'Nithin' },
|
||||||
|
{ name: 'Subin' },
|
||||||
|
{ name: 'Sojan' },
|
||||||
|
])
|
||||||
|
).toEqual('Pranav and 3 others are available');
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,3 +18,20 @@ export const IFrameHelper = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAvailableAgentsText = (agents = []) => {
|
||||||
|
const count = agents.length;
|
||||||
|
if (count === 1) {
|
||||||
|
const [agent] = agents;
|
||||||
|
return `${agent.name} is available`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count === 2) {
|
||||||
|
const [first, second] = agents;
|
||||||
|
return `${first.name} and ${second.name} is available`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [agent] = agents;
|
||||||
|
const rest = agents.length - 1;
|
||||||
|
return `${agent.name} and ${rest} others are available`;
|
||||||
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Vuex from 'vuex';
|
||||||
import appConfig from 'widget/store/modules/appConfig';
|
import appConfig from 'widget/store/modules/appConfig';
|
||||||
import contact from 'widget/store/modules/contact';
|
import contact from 'widget/store/modules/contact';
|
||||||
import conversation from 'widget/store/modules/conversation';
|
import conversation from 'widget/store/modules/conversation';
|
||||||
|
import agent from 'widget/store/modules/agent';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
@ -11,5 +12,6 @@ export default new Vuex.Store({
|
||||||
appConfig,
|
appConfig,
|
||||||
contact,
|
contact,
|
||||||
conversation,
|
conversation,
|
||||||
|
agent,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
50
app/javascript/widget/store/modules/agent.js
Normal file
50
app/javascript/widget/store/modules/agent.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { getAvailableAgents } from 'widget/api/agent';
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
records: [],
|
||||||
|
uiFlags: {
|
||||||
|
isError: false,
|
||||||
|
hasFetched: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getters = {
|
||||||
|
availableAgents: $state =>
|
||||||
|
$state.records.filter(agent => agent.availability_status === 'online'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
fetchAvailableAgents: async ({ commit }, websiteToken) => {
|
||||||
|
try {
|
||||||
|
const { data } = await getAvailableAgents(websiteToken);
|
||||||
|
const { payload = [] } = data;
|
||||||
|
commit('setAgents', payload);
|
||||||
|
commit('setError', false);
|
||||||
|
commit('setHasFetched', true);
|
||||||
|
} catch (error) {
|
||||||
|
commit('setError', true);
|
||||||
|
commit('setHasFetched', true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
setAgents($state, data) {
|
||||||
|
Vue.set($state, 'records', data);
|
||||||
|
},
|
||||||
|
setError($state, value) {
|
||||||
|
Vue.set($state.uiFlags, 'isError', value);
|
||||||
|
},
|
||||||
|
setHasFetched($state, value) {
|
||||||
|
Vue.set($state.uiFlags, 'hasFetched', value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { API } from 'widget/helpers/axios';
|
||||||
|
import { actions } from '../../agent';
|
||||||
|
import { agents } from './data';
|
||||||
|
|
||||||
|
const commit = jest.fn();
|
||||||
|
jest.mock('widget/helpers/axios');
|
||||||
|
|
||||||
|
describe('#actions', () => {
|
||||||
|
describe('#fetchAvailableAgents', () => {
|
||||||
|
it('sends correct actions if API is success', async () => {
|
||||||
|
API.get.mockResolvedValue({ data: { payload: agents } });
|
||||||
|
await actions.fetchAvailableAgents({ commit }, 'Hi');
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
['setAgents', agents],
|
||||||
|
['setError', false],
|
||||||
|
['setHasFetched', true],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('sends correct actions if API is error', async () => {
|
||||||
|
API.get.mockRejectedValue({ message: 'Authentication required' });
|
||||||
|
await actions.fetchAvailableAgents({ commit }, 'Hi');
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
['setError', true],
|
||||||
|
['setHasFetched', true],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
26
app/javascript/widget/store/modules/specs/agent/data.js
Normal file
26
app/javascript/widget/store/modules/specs/agent/data.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
export const agents = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'John',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Xavier',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'offline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Pranav',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Nithin',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { getters } from '../../agent';
|
||||||
|
import { agents } from './data';
|
||||||
|
|
||||||
|
describe('#getters', () => {
|
||||||
|
it('availableAgents', () => {
|
||||||
|
const state = {
|
||||||
|
records: agents,
|
||||||
|
};
|
||||||
|
expect(getters.availableAgents(state)).toEqual([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'John',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Pranav',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Nithin',
|
||||||
|
avatar_url: '',
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { mutations } from '../../agent';
|
||||||
|
import agents from './data';
|
||||||
|
|
||||||
|
describe('#mutations', () => {
|
||||||
|
describe('#setAgents', () => {
|
||||||
|
it('set agent records', () => {
|
||||||
|
const state = { records: [] };
|
||||||
|
mutations.setAgents(state, agents);
|
||||||
|
expect(state.records).toEqual(agents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setError', () => {
|
||||||
|
it('set error flag', () => {
|
||||||
|
const state = { records: [], uiFlags: {} };
|
||||||
|
mutations.setError(state, true);
|
||||||
|
expect(state.uiFlags.isError).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setError', () => {
|
||||||
|
it('set fetched flag', () => {
|
||||||
|
const state = { records: [], uiFlags: {} };
|
||||||
|
mutations.setHasFetched(state, true);
|
||||||
|
expect(state.uiFlags.hasFetched).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,6 +4,7 @@
|
||||||
<ChatHeaderExpanded v-if="isHeaderExpanded" />
|
<ChatHeaderExpanded v-if="isHeaderExpanded" />
|
||||||
<ChatHeader v-else :title="getHeaderName" />
|
<ChatHeader v-else :title="getHeaderName" />
|
||||||
</div>
|
</div>
|
||||||
|
<AvailableAgents v-if="showAvailableAgents" :agents="availableAgents" />
|
||||||
<ConversationWrap :grouped-messages="groupedMessages" />
|
<ConversationWrap :grouped-messages="groupedMessages" />
|
||||||
<div class="footer-wrap">
|
<div class="footer-wrap">
|
||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
|
@ -22,6 +23,7 @@ import ChatFooter from 'widget/components/ChatFooter.vue';
|
||||||
import ChatHeaderExpanded from 'widget/components/ChatHeaderExpanded.vue';
|
import ChatHeaderExpanded from 'widget/components/ChatHeaderExpanded.vue';
|
||||||
import ChatHeader from 'widget/components/ChatHeader.vue';
|
import ChatHeader from 'widget/components/ChatHeader.vue';
|
||||||
import ConversationWrap from 'widget/components/ConversationWrap.vue';
|
import ConversationWrap from 'widget/components/ConversationWrap.vue';
|
||||||
|
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
|
@ -31,19 +33,14 @@ export default {
|
||||||
ConversationWrap,
|
ConversationWrap,
|
||||||
ChatHeader,
|
ChatHeader,
|
||||||
Branding,
|
Branding,
|
||||||
},
|
AvailableAgents,
|
||||||
methods: {
|
|
||||||
...mapActions('conversation', ['sendMessage']),
|
|
||||||
handleSendMessage(content) {
|
|
||||||
this.sendMessage({
|
|
||||||
content,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
groupedMessages: 'conversation/getGroupedConversation',
|
groupedMessages: 'conversation/getGroupedConversation',
|
||||||
conversationSize: 'conversation/getConversationSize',
|
conversationSize: 'conversation/getConversationSize',
|
||||||
|
availableAgents: 'agent/availableAgents',
|
||||||
|
hasFetched: 'agent/uiFlags/hasFetched',
|
||||||
}),
|
}),
|
||||||
isHeaderExpanded() {
|
isHeaderExpanded() {
|
||||||
return this.conversationSize === 0;
|
return this.conversationSize === 0;
|
||||||
|
@ -51,6 +48,18 @@ export default {
|
||||||
getHeaderName() {
|
getHeaderName() {
|
||||||
return window.chatwootWebChannel.website_name;
|
return window.chatwootWebChannel.website_name;
|
||||||
},
|
},
|
||||||
|
showAvailableAgents() {
|
||||||
|
return this.availableAgents.length > 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapActions('conversation', ['sendMessage']),
|
||||||
|
handleSendMessage(content) {
|
||||||
|
this.sendMessage({
|
||||||
|
content,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -68,6 +77,13 @@ export default {
|
||||||
|
|
||||||
.header-wrap {
|
.header-wrap {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-radius: $space-normal;
|
||||||
|
background: white;
|
||||||
|
@include shadow-large;
|
||||||
|
|
||||||
|
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-wrap {
|
.footer-wrap {
|
||||||
|
@ -94,7 +110,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrap {
|
.input-wrap {
|
||||||
padding: 0 $space-medium;
|
padding: 0 $space-normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
json.payload do
|
json.payload do
|
||||||
json.array! @inbox_members do |inbox_member|
|
json.array! @inbox_members do |inbox_member|
|
||||||
|
json.id inbox_member.user.id
|
||||||
json.name inbox_member.user.name
|
json.name inbox_member.user.name
|
||||||
json.avatar_url inbox_member.user.avatar_url
|
json.avatar_url inbox_member.user.avatar_url
|
||||||
json.availability_status inbox_member.user.availability_status
|
json.availability_status inbox_member.user.availability_status
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
<script>
|
<script>
|
||||||
window.chatwootWebChannel = {
|
window.chatwootWebChannel = {
|
||||||
website_name: '<%= @web_widget.website_name %>',
|
website_name: '<%= @web_widget.website_name %>',
|
||||||
widget_color: '<%= @web_widget.widget_color %>'
|
widget_color: '<%= @web_widget.widget_color %>',
|
||||||
|
website_token: '<%= @web_widget.website_token %>'
|
||||||
}
|
}
|
||||||
window.chatwootPubsubToken = '<%= @contact.pubsub_token %>'
|
window.chatwootPubsubToken = '<%= @contact.pubsub_token %>'
|
||||||
window.authToken = '<%= @token %>'
|
window.authToken = '<%= @token %>'
|
||||||
|
|
Loading…
Reference in a new issue