Skip to content

Commit

Permalink
[Feature] Website live chat (chatwoot#187)
Browse files Browse the repository at this point in the history
Co-authored-by: Nithin David Thomas <[email protected]>
Co-authored-by: Sojan Jose <[email protected]>
  • Loading branch information
3 people authored Oct 29, 2019
1 parent a411428 commit 16fe912
Show file tree
Hide file tree
Showing 80 changed files with 2,039 additions and 105 deletions.
4 changes: 4 additions & 0 deletions .scss-lint.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
linters:
LeadingZero:
enabled: false

exclude:
- 'app/javascript/widget/assets/scss/_reset.scss'
- 'app/javascript/widget/assets/scss/sdk.css'
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ gem 'telegram-bot-ruby'
gem 'twitter'
# facebook client
gem 'koala'
# Random name generator
gem 'haikunator'

##--- gems for debugging and error reporting ---##
# static analysis
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ GEM
foreman (0.86.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
haikunator (1.1.0)
hashie (3.6.0)
http (3.3.0)
addressable (~> 2.3)
Expand Down Expand Up @@ -455,6 +456,7 @@ DEPENDENCIES
faker
figaro
foreman
haikunator
hashie
jbuilder (~> 2.5)
kaminari
Expand Down
26 changes: 26 additions & 0 deletions app/controllers/api/v1/widget/inboxes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Api::V1::Widget::InboxesController < ApplicationController
def create
ActiveRecord::Base.transaction do
channel = web_widgets.create!(
website_name: permitted_params[:website_name],
website_url: permitted_params[:website_url]
)
inbox = inboxes.create!(name: permitted_params[:website_name], channel: channel)
render json: inbox
end
end

private

def inboxes
current_account.inboxes
end

def web_widgets
current_account.web_widgets
end

def permitted_params
params.fetch(:website).permit(:website_name, :website_url)
end
end
74 changes: 57 additions & 17 deletions app/controllers/api/v1/widget/messages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,68 @@
class Api::V1::Widget::MessagesController < ApplicationController
# TODO: move widget apis to different controller.
skip_before_action :set_current_user, only: [:create_incoming]
skip_before_action :check_subscription, only: [:create_incoming]
skip_around_action :handle_with_exception, only: [:create_incoming]
class Api::V1::Widget::MessagesController < ActionController::Base
before_action :set_conversation, only: [:create]

def create_incoming
builder = Integrations::Widget::IncomingMessageBuilder.new(incoming_message_params)
builder.perform
render json: builder.message
def index
@messages = conversation.nil? ? [] : message_finder.perform
end

def create_outgoing
builder = Integrations::Widget::OutgoingMessageBuilder.new(outgoing_message_params)
builder.perform
render json: builder.message
def create
@message = conversation.messages.new(message_params)
@message.save!
end

private

def incoming_message_params
params.require(:message).permit(:contact_id, :inbox_id, :content)
def conversation
@conversation ||= ::Conversation.find_by(
contact_id: cookie_params[:contact_id],
inbox_id: cookie_params[:inbox_id]
)
end

def outgoing_message_params
params.require(:message).permit(:user_id, :inbox_id, :content, :conversation_id)
def set_conversation
@conversation = ::Conversation.create!(conversation_params) if conversation.nil?
end

def message_params
{
account_id: conversation.account_id,
inbox_id: conversation.inbox_id,
message_type: :incoming,
content: permitted_params[:content]
}
end

def conversation_params
{
account_id: inbox.account_id,
inbox_id: inbox.id,
contact_id: cookie_params[:contact_id]
}
end

def inbox
@inbox ||= ::Inbox.find_by(id: cookie_params[:inbox_id])
end

def cookie_params
JSON.parse(cookies.signed[cookie_name]).symbolize_keys
end

def message_finder_params
{
filter_internal_messages: true
}
end

def message_finder
@message_finder ||= MessageFinder.new(conversation, message_finder_params)
end

def cookie_name
'cw_conversation_' + params[:website_token]
end

def permitted_params
params.fetch(:message).permit(:content)
end
end
47 changes: 47 additions & 0 deletions app/controllers/widgets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
class WidgetsController < ActionController::Base
before_action :set_web_widget
before_action :set_contact
before_action :build_contact

private

def set_web_widget
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
end

def set_contact
return if cookie_params[:source_id].nil?

contact_inbox = ::ContactInbox.find_by(
inbox_id: @web_widget.inbox.id,
source_id: cookie_params[:source_id]
)

@contact = contact_inbox.contact
end

def build_contact
return if @contact.present?

contact_inbox = @web_widget.create_contact_inbox
@contact = contact_inbox.contact

cookies.signed[cookie_name] = JSON.generate(
source_id: contact_inbox.source_id,
contact_id: @contact.id,
inbox_id: @web_widget.inbox.id
).to_s
end

def cookie_params
cookies.signed[cookie_name] ? JSON.parse(cookies.signed[cookie_name]).symbolize_keys : {}
end

def permitted_params
params.permit(:website_token)
end

def cookie_name
'cw_conversation_' + permitted_params[:website_token]
end
end
10 changes: 8 additions & 2 deletions app/finders/message_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ def perform

private

def messages
return @conversation.messages if @params[:filter_internal_messages].blank?

@conversation.messages.where.not('private = ? OR message_type = ?', true, 2)
end

def current_messages
if @params[:before].present?
@conversation.messages.reorder('created_at desc').where('id < ?', @params[:before]).limit(20).reverse
messages.reorder('created_at desc').where('id < ?', @params[:before]).limit(20).reverse
else
@conversation.messages.reorder('created_at desc').limit(20).reverse
messages.reorder('created_at desc').limit(20).reverse
end
end
end
9 changes: 9 additions & 0 deletions app/javascript/dashboard/api/channel/webChannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ApiClient from '../ApiClient';

class WebChannel extends ApiClient {
constructor() {
super('widget/inboxes');
}
}

export default new WebChannel();
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions app/javascript/dashboard/assets/scss/_foundation-custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion app/javascript/dashboard/components/ChatList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/dashboard/components/layout/SidebarItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
52 changes: 40 additions & 12 deletions app/javascript/dashboard/components/widgets/ChannelItem.vue
Original file line number Diff line number Diff line change
@@ -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);
}
},
},
};
Expand Down
3 changes: 0 additions & 3 deletions app/javascript/dashboard/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 3 additions & 20 deletions app/javascript/dashboard/helper/actionCable.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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 => {
Expand Down
13 changes: 13 additions & 0 deletions app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
Loading

0 comments on commit 16fe912

Please sign in to comment.