[Enhancement] Add default avatar when profile image is not available (#188)

* Add default avatar when agent image is not available

* Remove fonts from avatar
Separate non-computed style values
This commit is contained in:
José Miguel Ochoa 2019-10-29 13:06:21 -05:00 committed by Sojan Jose
parent 16fe912fbd
commit e32b6bf6d4
7 changed files with 186 additions and 3 deletions

View file

@ -0,0 +1,80 @@
<template>
<div
class="avatar-container"
:style="[style, customStyle]"
aria-hidden="true"
>
<span>{{ userInitial }}</span>
</div>
</template>
<script>
export default {
name: 'Avatar',
props: {
username: {
type: String,
},
backgroundColor: {
type: String,
},
color: {
type: String,
},
customStyle: {
type: Object,
},
size: {
type: Number,
default: 40,
},
src: {
type: String,
},
rounded: {
type: Boolean,
default: true,
},
},
computed: {
style() {
return {
width: `${this.size}px`,
height: `${this.size}px`,
borderRadius: this.rounded ? '50%' : 0,
lineHeight: `${this.size + Math.floor(this.size / 20)}px`,
backgroundColor: this.backgroundColor,
fontSize: `${Math.floor(this.size / 2.5)}px`,
color: this.color,
};
},
userInitial() {
return this.initials || this.initial(this.username);
},
},
methods: {
initial(username) {
const parts = username ? username.split(/[ -]/) : [];
let initials = '';
for (let i = 0; i < parts.length; i += 1) {
initials += parts[i].charAt(0);
}
if (initials.length > 2 && initials.search(/[A-Z]/) !== -1) {
initials = initials.replace(/[a-z]+/g, '');
}
initials = initials.substr(0, 2).toUpperCase();
return initials;
},
},
};
</script>
<style lang="scss" scoped>
.avatar-container {
display: flex;
font-weight: bold;
align-items: center;
justify-content: center;
text-align: center;
}
</style>

View file

@ -0,0 +1,48 @@
import { mount } from '@vue/test-utils';
import Avatar from './Avatar.vue';
import Thumbnail from './Thumbnail.vue';
describe(`when there are NO errors loading the thumbnail`, () => {
it(`should render the agent thumbnail`, () => {
const wrapper = mount(Thumbnail, {
propsData: {
src: 'https://some_valid_url.com',
},
data() {
return {
imgError: false,
};
},
});
expect(wrapper.find('#image').exists()).toBe(true);
expect(wrapper.contains(Avatar)).toBe(false);
});
});
describe(`when there ARE errors loading the thumbnail`, () => {
it(`should render the agent avatar`, () => {
const wrapper = mount(Thumbnail, {
propsData: {
src: 'https://some_invalid_url.com',
},
data() {
return {
imgError: true,
};
},
});
expect(wrapper.contains('#image')).toBe(false);
expect(wrapper.contains(Avatar)).toBe(true);
});
});
describe(`when Avatar shows`, () => {
it(`initials shold correspond to username`, () => {
const wrapper = mount(Avatar, {
propsData: {
username: 'Angie Rojas',
},
});
expect(wrapper.find('span').text()).toBe('AR');
});
});

View file

@ -1,7 +1,26 @@
<template> <template>
<div class="user-thumbnail-box" v-bind:style="{ height: size, width: size }"> <div class="user-thumbnail-box" :style="{ height: size, width: size }">
<img v-bind:src="src" class="user-thumbnail"> <img
<img class="source-badge" src="~dashboard/assets/images/fb-badge.png" v-if="badge === 'Facebook'"> v-if="!imgError && Boolean(src)"
id="image"
:src="src"
class="user-thumbnail"
@error="onImgError()"
/>
<Avatar
v-else
:username="username"
class="user-thumbnail"
background-color="#1f93ff"
color="white"
>
</Avatar>
<img
v-if="badge === 'Facebook'"
id="badge"
class="source-badge"
src="~dashboard/assets/images/fb-badge.png"
/>
</div> </div>
</template> </template>
<script> <script>
@ -10,8 +29,14 @@
* Src - source for round image * Src - source for round image
* Size - Size of the thumbnail * Size - Size of the thumbnail
* Badge - Chat source indication { fb / telegram } * Badge - Chat source indication { fb / telegram }
* Username - User name for avatar
*/ */
import Avatar from './Avatar';
export default { export default {
components: {
Avatar,
},
props: { props: {
src: { src: {
type: String, type: String,
@ -24,6 +49,19 @@ export default {
type: String, type: String,
default: 'fb', default: 'fb',
}, },
username: {
type: String,
},
},
data() {
return {
imgError: false,
};
},
methods: {
onImgError() {
this.imgError = true;
},
}, },
}; };
</script> </script>

View file

@ -8,6 +8,8 @@
:src="chat.meta.sender.thumbnail" :src="chat.meta.sender.thumbnail"
:badge="chat.meta.sender.channel" :badge="chat.meta.sender.channel"
class="columns" class="columns"
:username="chat.meta.sender.name"
size="40px"
/> />
<div class="conversation--details columns"> <div class="conversation--details columns">
<h4 class="conversation--user"> <h4 class="conversation--user">

View file

@ -5,6 +5,7 @@
:src="chat.meta.sender.thumbnail" :src="chat.meta.sender.thumbnail"
size="40px" size="40px"
:badge="chat.meta.sender.channel" :badge="chat.meta.sender.channel"
:username="chat.meta.sender.name"
/> />
<h3 class="user--name"> <h3 class="user--name">
{{ chat.meta.sender.name }} {{ chat.meta.sender.name }}

View file

@ -53,6 +53,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vue/babel-preset-app": "^3.11.0", "@vue/babel-preset-app": "^3.11.0",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",

View file

@ -1234,6 +1234,14 @@
source-map "~0.6.1" source-map "~0.6.1"
vue-template-es2015-compiler "^1.9.0" vue-template-es2015-compiler "^1.9.0"
"@vue/test-utils@^1.0.0-beta.29":
version "1.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.29.tgz#c942cf25e891cf081b6a03332b4ae1ef430726f0"
integrity sha512-yX4sxEIHh4M9yAbLA/ikpEnGKMNBCnoX98xE1RwxfhQVcn0MaXNSj1Qmac+ZydTj6VBSEVukchBogXBTwc+9iA==
dependencies:
dom-event-types "^1.0.0"
lodash "^4.17.4"
"@webassemblyjs/ast@1.8.5": "@webassemblyjs/ast@1.8.5":
version "1.8.5" version "1.8.5"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@ -3352,6 +3360,11 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
dom-event-types@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dom-event-types/-/dom-event-types-1.0.0.tgz#5830a0a29e1bf837fe50a70cd80a597232813cae"
integrity sha512-2G2Vwi2zXTHBGqXHsJ4+ak/iP0N8Ar+G8a7LiD2oup5o4sQWytwqqrZu/O6hIMV0KMID2PL69OhpshLO0n7UJQ==
dom-serializer@0: dom-serializer@0:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb"