Skip to content

Commit

Permalink
Feature: As an end-user, I should be able to see the list of agents i…
Browse files Browse the repository at this point in the history
…n the widget. (chatwoot#461)

Co-authored-by: Pranav Raj S <[email protected]>
  • Loading branch information
nithindavid and pranavrajs authored Feb 5, 2020
1 parent 33e0bd4 commit 83b0bb4
Show file tree
Hide file tree
Showing 20 changed files with 406 additions and 34 deletions.
17 changes: 15 additions & 2 deletions app/javascript/dashboard/components/widgets/Thumbnail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
v-if="!imgError && Boolean(src)"
id="image"
:src="src"
class="user-thumbnail"
:class="thumbnailClass"
@error="onImgError()"
/>
<Avatar
v-else
:username="username"
class="user-thumbnail"
:class="thumbnailClass"
background-color="#1f93ff"
color="white"
:size="avatarSize"
Expand Down Expand Up @@ -71,6 +71,10 @@ export default {
type: String,
default: '',
},
hasBorder: {
type: Boolean,
default: false,
},
},
data() {
return {
Expand All @@ -89,6 +93,10 @@ export default {
const statusSize = `${this.avatarSize / 4}px`;
return { width: statusSize, height: statusSize };
},
thumbnailClass() {
const classname = this.hasBorder ? 'border' : '';
return `user-thumbnail ${classname}`;
},
},
methods: {
onImgError() {
Expand All @@ -111,6 +119,11 @@ export default {
border-radius: 50%;
height: 100%;
width: 100%;
box-sizing: border-box;
&.border {
border: 1px solid white;
}
}
.source-badge {
Expand Down
17 changes: 10 additions & 7 deletions app/javascript/widget/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IFrameHelper } from 'widget/helpers/utils';
export default {
name: 'App',
mounted() {
const { website_token: websiteToken = '' } = window.chatwootWebChannel;
if (IFrameHelper.isIFrame()) {
IFrameHelper.sendMessage({
event: 'loaded',
Expand All @@ -25,15 +26,16 @@ export default {
this.setWidgetColor(window.chatwootWebChannel);
window.addEventListener('message', e => {
if (
typeof e.data !== 'string' ||
e.data.indexOf('chatwoot-widget:') !== 0
) {
return;
}
const message = JSON.parse(e.data.replace('chatwoot-widget:', ''));
const wootPrefix = 'chatwoot-widget:';
const isDataNotString = typeof e.data !== 'string';
const isNotFromWoot = isDataNotString || e.data.indexOf(wootPrefix) !== 0;
if (isNotFromWoot) return;
const message = JSON.parse(e.data.replace(wootPrefix, ''));
if (message.event === 'config-set') {
this.fetchOldConversations();
this.fetchAvailableAgents(websiteToken);
} else if (message.event === 'widget-visible') {
this.scrollConversationToBottom();
} else if (message.event === 'set-current-url') {
Expand All @@ -44,6 +46,7 @@ export default {
methods: {
...mapActions('appConfig', ['setWidgetColor']),
...mapActions('conversation', ['fetchOldConversations']),
...mapActions('agent', ['fetchAvailableAgents']),
scrollConversationToBottom() {
const container = this.$el.querySelector('.conversation-wrap');
container.scrollTop = container.scrollHeight;
Expand Down
8 changes: 8 additions & 0 deletions app/javascript/widget/api/agent.js
Original file line number Diff line number Diff line change
@@ -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;
};
8 changes: 8 additions & 0 deletions app/javascript/widget/api/endPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ const updateContact = id => ({
url: `/api/v1/widget/messages/${id}${window.location.search}`,
});

const getAvailableAgents = token => ({
url: '/api/v1/widget/inbox_members',
params: {
website_token: token,
},
});

export default {
sendMessage,
getConversation,
updateContact,
getAvailableAgents,
};
6 changes: 3 additions & 3 deletions app/javascript/widget/assets/scss/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ $color-shadow-outline: rgba(66, 153, 225, 0.5);
}

@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;
}


@mixin shadow-medium {
box-shadow: 0 4px 6px -8px $color-shadow-medium,
0 2px 4px -4px $color-shadow-light;
box-shadow: 0 4px 24px 8px $color-shadow-medium,
0 2px 16px 4px $color-shadow-light;
}


Expand Down
79 changes: 79 additions & 0 deletions app/javascript/widget/components/AvailableAgents.vue
Original file line number Diff line number Diff line change
@@ -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>
4 changes: 0 additions & 4 deletions app/javascript/widget/components/ChatHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,10 @@ export default {
.header-collapsed {
display: flex;
justify-content: space-between;
background: $color-white;
padding: $space-two $space-medium;
width: 100%;
box-sizing: border-box;
color: $color-white;
border-bottom-left-radius: $space-small;
border-bottom-right-radius: $space-small;
@include shadow-large;
.title {
font-size: $font-size-large;
Expand Down
9 changes: 1 addition & 8 deletions app/javascript/widget/components/ChatHeaderExpanded.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,9 @@ export default {
@import '~widget/assets/scss/mixins.scss';
.header-expanded {
background: $color-white;
padding: $space-larger $space-medium $space-large;
width: 100%;
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 {
width: 64px;
Expand All @@ -71,7 +64,7 @@ export default {
.body {
color: $color-body;
font-size: 1.8rem;
line-height: 1.6;
line-height: 1.5;
}
}
</style>
47 changes: 47 additions & 0 deletions app/javascript/widget/components/GroupedAvatars.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 26 additions & 0 deletions app/javascript/widget/helpers/specs/utils.spec.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
17 changes: 17 additions & 0 deletions app/javascript/widget/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
};
2 changes: 2 additions & 0 deletions app/javascript/widget/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Vuex from 'vuex';
import appConfig from 'widget/store/modules/appConfig';
import contact from 'widget/store/modules/contact';
import conversation from 'widget/store/modules/conversation';
import agent from 'widget/store/modules/agent';

Vue.use(Vuex);

Expand All @@ -11,5 +12,6 @@ export default new Vuex.Store({
appConfig,
contact,
conversation,
agent,
},
});
Loading

0 comments on commit 83b0bb4

Please sign in to comment.