[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
80 changed files with 2040 additions and 106 deletions

View File

@@ -0,0 +1,9 @@
import ApiClient from '../ApiClient';
class WebChannel extends ApiClient {
constructor() {
super('widget/inboxes');
}
}
export default new WebChannel();

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -25,3 +25,14 @@
border-radius: $space-smaller;
font-size: $font-size-mini;
}
code {
border: 0;
font-family: 'Monaco';
font-size: $font-size-mini;
&.hljs {
background: $color-background;
padding: $space-two;
}
}

View File

@@ -121,7 +121,7 @@ export default {
fetchData() {
if (this.chatLists.length === 0) {
this.$store.dispatch('fetchAllConversations', {
inbox: this.conversationInbox,
inboxId: this.conversationInbox ? this.conversationInbox : undefined,
assigneeStatus: this.allMessageType,
convStatus: this.activeStatusTab,
});

View File

@@ -22,7 +22,7 @@
<ul v-if="menuItem.hasSubMenu" class="nested vertical menu">
<router-link
v-for="child in menuItem.children"
:key="child.label"
:key="child.id"
active-class="active flex-container"
:class="computedInboxClass(child)"
tag="li"

View File

@@ -1,22 +1,50 @@
<template>
<div class="small-3 columns channel" :class="{ inactive: channel !== 'facebook' }" @click.capture="itemClick">
<img src="~dashboard/assets/images/channels/facebook.png" v-if="channel === 'facebook'">
<img src="~dashboard/assets/images/channels/twitter.png" v-if="channel === 'twitter'">
<img src="~dashboard/assets/images/channels/telegram.png" v-if="channel === 'telegram'">
<img src="~dashboard/assets/images/channels/line.png" v-if="channel === 'line'">
<h3 class="channel__title">{{channel}}</h3>
<!-- <p>This is the most sexiest integration to begin </p> -->
<div
class="small-3 columns channel"
:class="{ inactive: !isActive(channel) }"
@click="onItemClick"
>
<img
v-if="channel === 'facebook'"
src="~dashboard/assets/images/channels/facebook.png"
/>
<img
v-if="channel === 'twitter'"
src="~dashboard/assets/images/channels/twitter.png"
/>
<img
v-if="channel === 'telegram'"
src="~dashboard/assets/images/channels/telegram.png"
/>
<img
v-if="channel === 'line'"
src="~dashboard/assets/images/channels/line.png"
/>
<img
v-if="channel === 'website'"
src="~dashboard/assets/images/channels/website.png"
/>
<h3 class="channel__title">
{{ channel }}
</h3>
</div>
</template>
<script>
/* global bus */
export default {
props: ['channel'],
created() {
props: {
channel: {
type: String,
required: true,
},
},
methods: {
itemClick() {
bus.$emit('channelItemClick', this.channel);
isActive(channel) {
return ['facebook', 'website'].includes(channel);
},
onItemClick() {
if (this.isActive(this.channel)) {
this.$emit('channel-item-click', this.channel);
}
},
},
};

View File

@@ -4,9 +4,6 @@ export default {
return `${this.APP_BASE_URL}/`;
},
GRAVATAR_URL: 'https://www.gravatar.com/avatar/',
CHANNELS: {
FACEBOOK: 'facebook',
},
ASSIGNEE_TYPE_SLUG: {
MINE: 0,
UNASSIGNED: 1,

View File

@@ -1,20 +1,9 @@
import { createConsumer } from '@rails/actioncable';
import AuthAPI from '../api/auth';
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
class ActionCableConnector {
class ActionCableConnector extends BaseActionCableConnector {
constructor(app, pubsubToken) {
const consumer = createConsumer();
consumer.subscriptions.create(
{
channel: 'RoomChannel',
pubsub_token: pubsubToken,
},
{
received: this.onReceived,
}
);
this.app = app;
super(app, pubsubToken);
this.events = {
'message.created': this.onMessageCreated,
'conversation.created': this.onConversationCreated,
@@ -43,12 +32,6 @@ class ActionCableConnector {
this.app.$store.dispatch('addMessage', data);
};
onReceived = ({ event, data } = {}) => {
if (this.events[event]) {
this.events[event](data);
}
};
onReload = () => window.location.reload();
onStatusChange = data => {

View File

@@ -15,6 +15,19 @@
"FB": {
"HELP": "PS: By signing in, we only get access to your Page's messages. Your private messages can never be accessed by Chatwoot."
},
"WEBSITE_CHANNEL": {
"TITLE": "Website channel",
"DESC": "Create a channel for your website and start supporting your customers via our website widget.",
"CHANNEL_NAME": {
"LABEL": "Website Name",
"PLACEHOLDER": "Enter your website name (eg: Acme Inc)"
},
"CHANNEL_DOMAIN": {
"LABEL": "Website Domain",
"PLACEHOLDER": "Enter your website domain (eg: acme.com)"
},
"SUBMIT_BUTTON":"Create inbox"
},
"AUTH": {
"TITLE": "Channels",
"DESC": "Currently we support only Facebook Pages as a platform. We have more platforms like Twitter, Telegram and Line in the works, which will be out soon."

View File

@@ -9,14 +9,14 @@
v-for="channel in channelList"
:key="channel"
:channel="channel"
@channel-item-click="initChannelAuth"
/>
</div>
</div>
</template>
<script>
/* global bus */
import ChannelItem from '../../../../components/widgets/ChannelItem';
import ChannelItem from 'dashboard/components/widgets/ChannelItem';
import router from '../../../index';
import PageHeader from '../SettingsSubPageHeader';
@@ -27,22 +27,16 @@ export default {
},
data() {
return {
channelList: ['facebook', 'twitter', 'telegram', 'line'],
channelList: ['website', 'facebook', 'twitter', 'telegram', 'line'],
};
},
created() {
bus.$on('channelItemClick', channel => {
this.initChannelAuth(channel);
});
},
methods: {
initChannelAuth(channel) {
if (channel === 'facebook') {
router.push({
name: 'settings_inboxes_page_channel',
params: { page: 'new', sub_page: 'facebook' },
});
}
const params = {
page: 'new',
sub_page: channel,
};
router.push({ name: 'settings_inboxes_page_channel', params });
},
},
};

View File

@@ -15,7 +15,7 @@
<table v-if="inboxesList.length" class="woot-table">
<tbody>
<tr v-for="item in inboxesList" :key="item.label">
<tr v-for="item in inboxesList" :key="item.id">
<td>
<img
class="woot-thumbnail"
@@ -26,7 +26,12 @@
<!-- Short Code -->
<td>
<span class="agent-name">{{ item.label }}</span>
<span>Facebook</span>
<span v-if="item.channelType === 'Channel::FacebookPage'">
Facebook
</span>
<span v-if="item.channelType === 'Channel::WebWidget'">
Website
</span>
</td>
<!-- Action Buttons -->

View File

@@ -5,7 +5,10 @@
:header-image="inbox.avatarUrl"
:header-title="inbox.label"
/>
<div class="code-wrapper">
<div
v-if="inbox.channelType === 'Channel::FacebookPage'"
class="code-wrapper"
>
<p class="title">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_HEADING') }}
</p>
@@ -18,6 +21,20 @@
</code>
</p>
</div>
<div
v-else-if="inbox.channelType === 'Channel::WebWidget'"
class="code-wrapper"
>
<p class="title">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_HEADING') }}
</p>
<p class="sub-head">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_SUB_HEAD') }}
</p>
<highlight-code lang="javascript">
{{ webWidgetScript }}
</highlight-code>
</div>
<div class="agent-wrapper">
<p class="title">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS') }}
@@ -53,6 +70,7 @@
/* eslint-disable no-useless-escape */
/* global bus */
import { mapGetters } from 'vuex';
import 'highlight.js/styles/default.css';
export default {
props: ['onClose', 'inbox', 'show'],
@@ -83,6 +101,20 @@ export default {
color="blue"
size="standard" >
</div>`,
webWidgetScript: `
(function(d,t) {
var BASE_URL = '${window.location.origin}';
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src= BASE_URL + "/packs/js/sdk.js";
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
websiteToken: '${this.inbox.websiteToken}',
baseUrl: BASE_URL
})
}
})(document,"script");
`,
};
},
computed: {

View File

@@ -1,19 +1,23 @@
import CONSTANTS from '../../../../constants';
import FacebookView from './Facebook';
import Facebook from './channels/Facebook';
import Website from './channels/Website';
const channelViewList = {
facebook: Facebook,
website: Website,
};
export default {
create() {
return {
name: 'new-channel-view',
render(h) {
if (this.channel_name === CONSTANTS.CHANNELS.FACEBOOK) {
return h(FacebookView);
}
return null;
},
props: {
channel_name: String,
channel_name: {
type: String,
required: true,
},
},
name: 'new-channel-view',
render(h) {
return h(channelViewList[this.channel_name] || null);
},
};
},

View File

@@ -64,14 +64,13 @@
</div>
</template>
<script>
/* eslint no-console: 0 */
/* eslint-env browser */
/* global FB */
import { required } from 'vuelidate/lib/validators';
import ChannelApi from '../../../../api/channels';
import LoadingState from '../../../../components/widgets/LoadingState';
import PageHeader from '../SettingsSubPageHeader';
import router from '../../../index';
import LoadingState from 'dashboard/components/widgets/LoadingState';
import ChannelApi from '../../../../../api/channels';
import PageHeader from '../../SettingsSubPageHeader';
import router from '../../../../index';
export default {
components: {

View File

@@ -0,0 +1,83 @@
<template>
<div class="wizard-body small-9 columns">
<page-header
:header-title="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.TITLE')"
:header-content="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.DESC')"
/>
<loading-state
v-if="isCreating"
message="Creating Website Support Channel"
></loading-state>
<form v-if="!isCreating" class="row" @submit.prevent="createChannel()">
<div class="medium-12 columns">
<label>
{{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_NAME.LABEL') }}
<input
v-model.trim="websiteName"
type="text"
:placeholder="
$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_NAME.PLACEHOLDER')
"
/>
</label>
</div>
<div class="medium-12 columns">
<label>
{{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_DOMAIN.LABEL') }}
<input
v-model.trim="websiteUrl"
type="text"
:placeholder="
$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_DOMAIN.PLACEHOLDER')
"
/>
</label>
</div>
<div class="modal-footer">
<div class="medium-12 columns">
<woot-submit-button
:button-text="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.SUBMIT_BUTTON')"
/>
</div>
</div>
</form>
</div>
</template>
<script>
/* global bus */
import router from '../../../../index';
import PageHeader from '../../SettingsSubPageHeader';
export default {
components: {
PageHeader,
},
data() {
return {
websiteName: '',
websiteUrl: '',
isCreating: false,
};
},
mounted() {
bus.$on('new_website_channel', ({ inboxId }) => {
router.replace({
name: 'settings_inboxes_add_agents',
params: { page: 'new', inbox_id: inboxId },
});
});
},
methods: {
createChannel() {
this.isCreating = true;
this.$store.dispatch('addWebsiteChannel', {
website: {
website_name: this.websiteName,
website_url: this.websiteUrl,
},
});
},
},
};
</script>

View File

@@ -1,13 +1,14 @@
/* eslint no-console: 0 */
/* eslint-env browser */
/* eslint no-param-reassign: 0 */
/* global bus */
// import * as types from '../mutation-types';
import defaultState from '../../i18n/default-sidebar';
import * as types from '../mutation-types';
import Account from '../../api/account';
import ChannelApi from '../../api/channels';
import { frontendURL } from '../../helper/URLHelper';
import WebChannel from '../../api/channel/webChannel';
const state = defaultState;
// inboxes fetch flag
@@ -66,6 +67,15 @@ const actions = {
});
});
},
addWebsiteChannel: async ({ commit }, params) => {
try {
const response = await WebChannel.create(params);
commit(types.default.SET_INBOX_ITEM, response);
bus.$emit('new_website_channel', { inboxId: response.data.id });
} catch (error) {
// Handle error
}
},
addInboxItem({ commit }, { channel, params }) {
const donePromise = new Promise(resolve => {
ChannelApi.createChannel(channel, params)
@@ -137,9 +147,10 @@ const mutations = {
channel_id: item.id,
label: item.name,
toState: frontendURL(`inbox/${item.id}`),
channelType: item.channelType,
channelType: item.channel_type,
avatarUrl: item.avatar_url,
pageId: item.page_id,
websiteToken: item.website_token,
}));
// Identify menuItem to update
// May have more than one object to update
@@ -156,7 +167,7 @@ const mutations = {
channel_id: data.id,
label: data.name,
toState: frontendURL(`inbox/${data.id}`),
channelType: data.channelType,
channelType: data.channel_type,
avatarUrl: data.avatar_url === undefined ? null : data.avatar_url,
pageId: data.page_id,
});