[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:
9
app/javascript/dashboard/api/channel/webChannel.js
Normal file
9
app/javascript/dashboard/api/channel/webChannel.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class WebChannel extends ApiClient {
|
||||
constructor() {
|
||||
super('widget/inboxes');
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebChannel();
|
||||
BIN
app/javascript/dashboard/assets/images/channels/website.png
Normal file
BIN
app/javascript/dashboard/assets/images/channels/website.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user