[Feature] Website live chat (#187)

Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Pranav Raj S 2019-10-29 12:50:54 +05:30 committed by GitHub
parent a4114288f3
commit 16fe912fbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 2040 additions and 106 deletions

View file

@ -0,0 +1,83 @@
<template>
<div class="agent-message">
<div class="avatar-wrap">
<UserAvatar size="small" :src="avatarUrl" />
</div>
<div class="message-wrap">
<h5 class="agent-name">
{{ agentName }}
</h5>
<AgentMessageBubble :message="message" />
</div>
</div>
</template>
<script>
import UserAvatar from 'widget/components/UserAvatar.vue';
import AgentMessageBubble from 'widget/components/AgentMessageBubble.vue';
export default {
name: 'AgentMessage',
components: {
UserAvatar,
AgentMessageBubble,
},
props: {
message: String,
avatarUrl: String,
agentName: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.agent-message {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-end;
margin: 0 $space-smaller $space-micro auto;
& + .agent-message {
margin-bottom: $space-micro;
.chat-bubble {
border-top-left-radius: $space-smaller;
}
.user-avatar {
visibility: hidden;
}
.agent-name {
display: none;
}
}
& + .user-message {
margin-bottom: $space-normal;
}
.avatar-wrap {
flex-shrink: 1;
flex-grow: 0;
}
.message-wrap {
max-width: 90%;
flex-shrink: 0;
flex-grow: 1;
margin-left: $space-small;
.agent-name {
font-weight: $font-weight-medium;
margin-bottom: $space-smaller;
margin-left: $space-two;
color: $color-body;
}
}
}
</style>

View file

@ -0,0 +1,27 @@
<template>
<div class="chat-bubble agent">
{{ message }}
</div>
</template>
<script>
export default {
name: 'AgentMessageBubble',
props: {
message: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
@import '~widget/assets/scss/variables.scss';
.chat-bubble {
&.agent {
background: $color-white;
border-bottom-left-radius: $space-smaller;
color: $color-body;
}
}
</style>

View file

@ -0,0 +1,33 @@
<template>
<footer class="footer">
<ChatInputWrap :on-send-message="onSendMessage" />
</footer>
</template>
<script>
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
export default {
components: {
ChatInputWrap,
},
props: {
msg: String,
onSendMessage: Function,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.footer {
background: $color-white;
box-shadow: 0 -$space-micro 3px rgba(50, 50, 93, 0.04),
0 -1px 2px rgba(0, 0, 0, 0.03);
box-sizing: border-box;
padding: $space-small;
width: 100%;
}
</style>

View file

@ -0,0 +1,51 @@
<template>
<header class="header-expanded">
<div>
<h2 class="title">
{{ introHeading }}
</h2>
<p class="body">
{{ introBody }}
</p>
</div>
</header>
</template>
<script>
export default {
name: 'ChatHeaderExpanded',
props: {
introHeading: {
type: String,
default: 'Hi there ! 🙌🏼',
},
introBody: {
type: String,
default:
'We make it simple to connect with us. Ask us anything, or share your feedback.',
},
},
};
</script>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.header-expanded {
background: $color-woot;
padding: $space-large;
width: 100%;
box-sizing: border-box;
color: $color-white;
.title {
font-size: $font-size-mega;
margin-bottom: $space-two;
}
.body {
font-size: $font-size-medium;
line-height: 1.5;
}
}
</style>

View file

@ -0,0 +1,29 @@
<template>
<textarea
class="form-input user-message-input"
:placeholder="placeholder"
:value="value"
@input="$emit('input', $event.target.value)"
/>
</template>
<script>
export default {
props: {
placeholder: String,
value: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.user-message-input {
border-color: $color-white;
border-bottom-color: $color-border-light;
height: $space-big;
resize: none;
}
</style>

View file

@ -0,0 +1,65 @@
<template>
<div class="input-wrap">
<div>
<ChatInputArea v-model="userInput" :placeholder="placeholder" />
</div>
<div class="message-button-wrap">
<ChatSendButton
:on-click="handleButtonClick"
:disabled="!userInput.length"
/>
</div>
</div>
</template>
<script>
import ChatSendButton from 'widget/components/ChatSendButton.vue';
import ChatInputArea from 'widget/components/ChatInputArea.vue';
export default {
name: 'ChatInputWrap',
components: {
ChatSendButton,
ChatInputArea,
},
props: {
placeholder: {
type: String,
default: 'Type your message',
},
onSendMessage: {
type: Function,
default: () => {},
},
},
data() {
return {
userInput: '',
};
},
methods: {
handleButtonClick() {
if (this.userInput) {
this.onSendMessage(this.userInput);
}
this.userInput = '';
},
},
};
</script>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.input-wrap {
.message-button-wrap {
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: $space-small;
}
}
</style>

View file

@ -0,0 +1,38 @@
<template>
<UserMessage v-if="isUserMessage" :message="message.content" />
<AgentMessage
v-else
:agent-name="message.sender_name"
:message="message.content"
/>
</template>
<script>
import AgentMessage from 'widget/components/AgentMessage.vue';
import UserMessage from 'widget/components/UserMessage.vue';
import { MESSAGE_TYPE } from 'widget/helpers/constants';
export default {
components: {
AgentMessage,
UserMessage,
},
props: {
message: Object,
},
computed: {
isUserMessage() {
return this.message.message_type === MESSAGE_TYPE.INCOMING;
},
},
};
</script>
<style scoped lang="scss">
.message-wrap {
display: flex;
flex-direction: row;
align-items: flex-end;
max-width: 90%;
}
</style>

View file

@ -0,0 +1,63 @@
<template>
<button
type="submit"
:disabled="disabled"
class="button send-button"
@click="onClick"
>
<span v-if="!loading" class="icon-holder">
<img src="~widget/assets/images/message-send.svg" />
<span>Send</span>
</span>
<spinner v-else size="small" />
</button>
</template>
<script>
import Spinner from 'widget/components/Spinner.vue';
export default {
components: {
Spinner,
},
props: {
loading: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
onClick: {
type: Function,
default: () => {},
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.send-button {
align-items: center;
display: flex;
justify-content: space-around;
min-width: $space-big;
position: relative;
.icon-holder {
display: flex;
align-items: center;
justify-content: center;
fill: $color-white;
font-weight: $font-weight-medium;
img {
margin-right: $space-small;
}
}
}
</style>

View file

@ -0,0 +1,33 @@
<template>
<section class="conversation">
<ChatMessage
v-for="message in messages"
:key="message.id"
:message="message"
/>
</section>
</template>
<script>
import ChatMessage from 'widget/components/ChatMessage.vue';
export default {
name: 'ConversationWrap',
components: {
ChatMessage,
},
props: {
messages: Object,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.conversation {
height: 100%;
padding: $space-large $space-small $space-large $space-normal;
}
</style>

View file

@ -0,0 +1,31 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
props: {
msg: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View file

@ -0,0 +1,52 @@
<template>
<span class="spinner" :class="size"></span>
</template>
<script>
const SIZES = ['small', 'medium', 'large'];
export default {
props: {
size: {
validator: value => SIZES.includes(value),
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.spinner {
@keyframes spinner {
to {
transform: rotate(360deg);
}
}
&:before {
animation: spinner 0.7s linear infinite;
border-radius: 50%;
border-top-color: lighten($color-woot, 10%);
border: 2px solid rgba(255, 255, 255, 0.7);
box-sizing: border-box;
content: '';
height: $space-medium;
left: 50%;
margin-left: -$space-slab;
margin-top: -$space-slab;
position: absolute;
top: 50%;
width: $space-medium;
}
&.small:before {
border-width: 1px;
height: $space-slab;
margin-left: -$space-slab/2;
margin-top: -$space-slab/2;
width: $space-slab;
}
}
</style>

View file

@ -0,0 +1,46 @@
<template>
<div class="user-avatar" :class="size" :style="getBgImage"></div>
</template>
<script>
/**
* Thumbnail Component
* Src - source for round image
*/
export default {
name: 'UserAvatar',
props: {
src: {
type: String,
},
size: {
type: String,
},
},
computed: {
getBgImage() {
if (this.src) return { 'background-image': `url(${this.src})` };
return {};
},
},
};
</script>
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
@import '~widget/assets/scss/mixins.scss';
.user-avatar {
@include light-shadow;
background: url('~widget/assets/images/defaultUser.png') center center
no-repeat;
background-size: cover;
border-radius: 50%;
height: 40px;
width: 40px;
&.small {
width: $space-medium;
height: $space-medium;
}
}
</style>

View file

@ -0,0 +1,55 @@
<template>
<div class="user-message">
<div class="message-wrap">
<UserMessageBubble :message="message" />
</div>
</div>
</template>
<script>
import UserMessageBubble from 'widget/components/UserMessageBubble.vue';
export default {
name: 'UserMessage',
components: {
UserMessageBubble,
},
props: {
message: String,
avatarUrl: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '~widget/assets/scss/variables.scss';
.user-message {
align-items: flex-end;
display: flex;
flex-direction: row;
justify-content: flex-end;
margin: 0 $space-smaller $space-micro auto;
text-align: right;
& + .user-message {
margin-bottom: $space-micro;
.chat-bubble {
border-top-right-radius: $space-smaller;
}
.user-avatar {
visibility: hidden;
}
.agent-name {
display: none;
}
}
& + .agent-message {
margin-bottom: $space-normal;
}
.message-wrap {
margin-right: $space-small;
}
}
</style>

View file

@ -0,0 +1,36 @@
<template>
<div class="chat-bubble user">
{{ message }}
</div>
</template>
<script>
export default {
name: 'UserMessageBubble',
props: {
message: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
@import '~widget/assets/scss/variables.scss';
@import '~widget/assets/scss/mixins.scss';
.chat-bubble {
@include light-shadow;
background: $color-woot;
border-radius: $space-two;
color: $color-white;
display: inline-block;
font-size: $font-size-default;
line-height: 1.5;
max-width: 80%;
padding: $space-small $space-two;
&.user {
border-bottom-right-radius: $space-smaller;
}
}
</style>