Merge branch 'chore/upgrade-to-postcss-8' of https://github.com/chatwoot/chatwoot into feat/widget-multi-getters
This commit is contained in:
commit
7bdfb5b075
151 changed files with 2383 additions and 241 deletions
3
.bundler-audit.yml
Normal file
3
.bundler-audit.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
ignore:
|
||||||
|
- CVE-2021-41098 # https://github.com/chatwoot/chatwoot/issues/3097 (update once azure blob storage is updated)
|
8
Gemfile
8
Gemfile
|
@ -56,8 +56,7 @@ gem 'activerecord-import'
|
||||||
gem 'dotenv-rails'
|
gem 'dotenv-rails'
|
||||||
gem 'foreman'
|
gem 'foreman'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'rack-timeout'
|
gem 'webpacker', '~> 5.4.0'
|
||||||
gem 'webpacker', '~> 5.x'
|
|
||||||
# metrics on heroku
|
# metrics on heroku
|
||||||
gem 'barnes'
|
gem 'barnes'
|
||||||
|
|
||||||
|
@ -122,6 +121,11 @@ gem 'hairtrigger'
|
||||||
|
|
||||||
gem 'procore-sift'
|
gem 'procore-sift'
|
||||||
|
|
||||||
|
group :production, :staging do
|
||||||
|
# we dont want request timing out in development while using byebug
|
||||||
|
gem 'rack-timeout'
|
||||||
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'annotate'
|
gem 'annotate'
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -247,6 +247,7 @@ GEM
|
||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
|
google-protobuf (3.17.3)
|
||||||
google-protobuf (3.17.3-universal-darwin)
|
google-protobuf (3.17.3-universal-darwin)
|
||||||
google-protobuf (3.17.3-x86_64-linux)
|
google-protobuf (3.17.3-x86_64-linux)
|
||||||
googleapis-common-protos (1.3.11)
|
googleapis-common-protos (1.3.11)
|
||||||
|
@ -264,6 +265,9 @@ GEM
|
||||||
signet (~> 0.14)
|
signet (~> 0.14)
|
||||||
groupdate (5.2.2)
|
groupdate (5.2.2)
|
||||||
activesupport (>= 5)
|
activesupport (>= 5)
|
||||||
|
grpc (1.38.0)
|
||||||
|
google-protobuf (~> 3.15)
|
||||||
|
googleapis-common-protos-types (~> 1.0)
|
||||||
grpc (1.38.0-universal-darwin)
|
grpc (1.38.0-universal-darwin)
|
||||||
google-protobuf (~> 3.15)
|
google-protobuf (~> 3.15)
|
||||||
googleapis-common-protos-types (~> 1.0)
|
googleapis-common-protos-types (~> 1.0)
|
||||||
|
@ -346,6 +350,7 @@ GEM
|
||||||
mime-types-data (3.2021.0704)
|
mime-types-data (3.2021.0704)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.1.1)
|
mini_mime (1.1.1)
|
||||||
|
mini_portile2 (2.5.3)
|
||||||
minitest (5.14.4)
|
minitest (5.14.4)
|
||||||
mock_redis (0.28.0)
|
mock_redis (0.28.0)
|
||||||
ruby2_keywords
|
ruby2_keywords
|
||||||
|
@ -360,6 +365,9 @@ GEM
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
newrelic_rpm (7.2.0)
|
newrelic_rpm (7.2.0)
|
||||||
nio4r (2.5.8)
|
nio4r (2.5.8)
|
||||||
|
nokogiri (1.11.7)
|
||||||
|
mini_portile2 (~> 2.5.0)
|
||||||
|
racc (~> 1.4)
|
||||||
nokogiri (1.11.7-arm64-darwin)
|
nokogiri (1.11.7-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.11.7-x86_64-darwin)
|
nokogiri (1.11.7-x86_64-darwin)
|
||||||
|
@ -617,6 +625,7 @@ GEM
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-20
|
arm64-darwin-20
|
||||||
|
ruby
|
||||||
x86_64-darwin-18
|
x86_64-darwin-18
|
||||||
x86_64-darwin-20
|
x86_64-darwin-20
|
||||||
x86_64-darwin-21
|
x86_64-darwin-21
|
||||||
|
@ -714,7 +723,7 @@ DEPENDENCIES
|
||||||
valid_email2
|
valid_email2
|
||||||
web-console
|
web-console
|
||||||
webmock
|
webmock
|
||||||
webpacker (~> 5.x)
|
webpacker (~> 5.4.0)
|
||||||
webpush
|
webpush
|
||||||
wisper (= 2.0.0)
|
wisper (= 2.0.0)
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,14 @@ class Messages::Facebook::MessageBuilder
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_contact_params_result(result)
|
||||||
|
{
|
||||||
|
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||||
|
account_id: @inbox.account_id,
|
||||||
|
remote_avatar_url: result['profile_pic'] || ''
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def contact_params
|
def contact_params
|
||||||
begin
|
begin
|
||||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||||
|
@ -155,14 +163,15 @@ class Messages::Facebook::MessageBuilder
|
||||||
rescue Koala::Facebook::AuthenticationError
|
rescue Koala::Facebook::AuthenticationError
|
||||||
@inbox.channel.authorization_error!
|
@inbox.channel.authorization_error!
|
||||||
raise
|
raise
|
||||||
|
rescue Koala::Facebook::ClientError => e
|
||||||
|
result = {}
|
||||||
|
# OAuthException, code: 100, error_subcode: 2018218, message: (#100) No profile available for this user
|
||||||
|
# We don't need to capture this error as we don't care about contact params in case of echo messages
|
||||||
|
Sentry.capture_exception(e) unless @outgoing_echo
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
result = {}
|
result = {}
|
||||||
Sentry.capture_exception(e)
|
Sentry.capture_exception(e)
|
||||||
end
|
end
|
||||||
{
|
process_contact_params_result(result)
|
||||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
|
||||||
account_id: @inbox.account_id,
|
|
||||||
remote_avatar_url: result['profile_pic'] || ''
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,19 +41,25 @@ class V2::ReportBuilder
|
||||||
user
|
user
|
||||||
when :label
|
when :label
|
||||||
label
|
label
|
||||||
|
when :team
|
||||||
|
team
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def inbox
|
def inbox
|
||||||
@inbox ||= account.inboxes.where(id: params[:id]).first
|
@inbox ||= account.inboxes.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def user
|
def user
|
||||||
@user ||= account.users.where(id: params[:id]).first
|
@user ||= account.users.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def label
|
def label
|
||||||
@label ||= account.labels.where(id: params[:id]).first
|
@label ||= account.labels.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def team
|
||||||
|
@team ||= account.teams.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversations_count
|
def conversations_count
|
||||||
|
|
|
@ -10,7 +10,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||||
|
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :set_current_page, only: [:index, :active, :search]
|
before_action :set_current_page, only: [:index, :active, :search]
|
||||||
before_action :fetch_contact, only: [:show, :update, :contactable_inboxes]
|
before_action :fetch_contact, only: [:show, :update, :destroy, :contactable_inboxes]
|
||||||
before_action :set_include_contact_inboxes, only: [:index, :search]
|
before_action :set_include_contact_inboxes, only: [:index, :search]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -30,10 +30,13 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def import
|
def import
|
||||||
|
render json: { error: I18n.t('errors.contacts.import.failed') }, status: :unprocessable_entity and return if params[:import_file].blank?
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
import = Current.account.data_imports.create!(data_type: 'contacts')
|
import = Current.account.data_imports.create!(data_type: 'contacts')
|
||||||
import.import_file.attach(params[:import_file])
|
import.import_file.attach(params[:import_file])
|
||||||
end
|
end
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -70,6 +73,18 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||||
}, status: :unprocessable_entity
|
}, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
if ::OnlineStatusTracker.get_presence(
|
||||||
|
@contact.account.id, 'Contact', @contact.id
|
||||||
|
)
|
||||||
|
return render_error({ message: I18n.t('contacts.online.delete', contact_name: @contact.name.capitalize) },
|
||||||
|
:unprocessable_entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
@contact.destroy!
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# TODO: Move this to a finder class
|
# TODO: Move this to a finder class
|
||||||
|
@ -134,4 +149,8 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||||
def fetch_contact
|
def fetch_contact
|
||||||
@contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id])
|
@contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_error(error, error_status)
|
||||||
|
render json: error, status: error_status
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -69,6 +69,12 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
|
|
||||||
def update_last_seen
|
def update_last_seen
|
||||||
@conversation.agent_last_seen_at = DateTime.now.utc
|
@conversation.agent_last_seen_at = DateTime.now.utc
|
||||||
|
@conversation.assignee_last_seen_at = DateTime.now.utc if assignee?
|
||||||
|
@conversation.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def custom_attributes
|
||||||
|
@conversation.custom_attributes = params.permit(custom_attributes: {})[:custom_attributes]
|
||||||
@conversation.save!
|
@conversation.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -112,6 +118,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
|
|
||||||
def conversation_params
|
def conversation_params
|
||||||
additional_attributes = params[:additional_attributes]&.permit! || {}
|
additional_attributes = params[:additional_attributes]&.permit! || {}
|
||||||
|
custom_attributes = params[:custom_attributes]&.permit! || {}
|
||||||
status = params[:status].present? ? { status: params[:status] } : {}
|
status = params[:status].present? ? { status: params[:status] } : {}
|
||||||
|
|
||||||
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
|
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
|
||||||
|
@ -122,6 +129,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
contact_id: @contact_inbox.contact_id,
|
contact_id: @contact_inbox.contact_id,
|
||||||
contact_inbox_id: @contact_inbox.id,
|
contact_inbox_id: @contact_inbox.id,
|
||||||
additional_attributes: additional_attributes,
|
additional_attributes: additional_attributes,
|
||||||
|
custom_attributes: custom_attributes,
|
||||||
snoozed_until: params[:snoozed_until]
|
snoozed_until: params[:snoozed_until]
|
||||||
}.merge(status)
|
}.merge(status)
|
||||||
end
|
end
|
||||||
|
@ -129,4 +137,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||||
def conversation_finder
|
def conversation_finder
|
||||||
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assignee?
|
||||||
|
@conversation.assignee_id? && current_user == @conversation.assignee
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,6 +100,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_channel_feature_flags
|
def update_channel_feature_flags
|
||||||
|
return unless @inbox.web_widget?
|
||||||
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
|
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
|
||||||
|
|
||||||
@inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags]
|
@inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags]
|
||||||
|
|
|
@ -29,6 +29,12 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||||
render layout: false, template: 'api/v2/accounts/reports/labels.csv.erb', format: 'csv'
|
render layout: false, template: 'api/v2/accounts/reports/labels.csv.erb', format: 'csv'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def teams
|
||||||
|
response.headers['Content-Type'] = 'text/csv'
|
||||||
|
response.headers['Content-Disposition'] = 'attachment; filename=teams_report.csv'
|
||||||
|
render layout: false, template: 'api/v2/accounts/reports/teams.csv.erb', format: 'csv'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_authorization
|
def check_authorization
|
||||||
|
|
|
@ -3,10 +3,9 @@ class SuperAdmin::DashboardController < SuperAdmin::ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a
|
@data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a
|
||||||
@accounts_count = number_with_delimiter(Account.all.length)
|
@accounts_count = number_with_delimiter(Account.count)
|
||||||
@users_count = number_with_delimiter(User.all.length)
|
@users_count = number_with_delimiter(User.count)
|
||||||
@inboxes_count = number_with_delimiter(Inbox.all.length)
|
@inboxes_count = number_with_delimiter(Inbox.count)
|
||||||
@conversations_count = number_with_delimiter(Conversation.all.length)
|
@conversations_count = number_with_delimiter(Conversation.count)
|
||||||
@messages_count = number_with_delimiter(Message.all.length)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,6 +52,14 @@ class ContactAPI extends ApiClient {
|
||||||
)}`;
|
)}`;
|
||||||
return axios.get(requestURL);
|
return axios.get(requestURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importContacts(file) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('import_file', file);
|
||||||
|
return axios.post(`${this.url}/import`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ContactAPI();
|
export default new ContactAPI();
|
||||||
|
|
|
@ -59,6 +59,18 @@ describe('#ContactsAPI', () => {
|
||||||
'/api/v1/contacts/search?include_contact_inboxes=false&page=1&sort=date&q=leads&labels[]=customer-support'
|
'/api/v1/contacts/search?include_contact_inboxes=false&page=1&sort=date&q=leads&labels[]=customer-support'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#importContacts', () => {
|
||||||
|
const file = 'file';
|
||||||
|
contactAPI.importContacts(file);
|
||||||
|
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/contacts/import',
|
||||||
|
expect.any(FormData),
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
BIN
app/javascript/dashboard/assets/images/twitter-chat-badge.png
Normal file
BIN
app/javascript/dashboard/assets/images/twitter-chat-badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
.margin-right-small {
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
|
@ -71,7 +71,8 @@
|
||||||
@include padding($space-large);
|
@include padding($space-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form,
|
||||||
|
.modal-content {
|
||||||
@include padding($space-large);
|
@include padding($space-large);
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
|
|
|
@ -194,7 +194,7 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleKeyEvents(e) {
|
getKeyboardListenerParams() {
|
||||||
const allConversations = this.$refs.activeConversation.querySelectorAll(
|
const allConversations = this.$refs.activeConversation.querySelectorAll(
|
||||||
'div.conversations-list div.conversation'
|
'div.conversations-list div.conversation'
|
||||||
);
|
);
|
||||||
|
@ -205,7 +205,19 @@ export default {
|
||||||
activeConversation
|
activeConversation
|
||||||
);
|
);
|
||||||
const lastConversationIndex = allConversations.length - 1;
|
const lastConversationIndex = allConversations.length - 1;
|
||||||
|
return {
|
||||||
|
allConversations,
|
||||||
|
activeConversation,
|
||||||
|
activeConversationIndex,
|
||||||
|
lastConversationIndex,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleKeyEvents(e) {
|
||||||
if (hasPressedAltAndJKey(e)) {
|
if (hasPressedAltAndJKey(e)) {
|
||||||
|
const {
|
||||||
|
allConversations,
|
||||||
|
activeConversationIndex,
|
||||||
|
} = this.getKeyboardListenerParams();
|
||||||
if (activeConversationIndex === -1) {
|
if (activeConversationIndex === -1) {
|
||||||
allConversations[0].click();
|
allConversations[0].click();
|
||||||
}
|
}
|
||||||
|
@ -214,6 +226,11 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasPressedAltAndKKey(e)) {
|
if (hasPressedAltAndKKey(e)) {
|
||||||
|
const {
|
||||||
|
allConversations,
|
||||||
|
activeConversationIndex,
|
||||||
|
lastConversationIndex,
|
||||||
|
} = this.getKeyboardListenerParams();
|
||||||
if (activeConversationIndex === -1) {
|
if (activeConversationIndex === -1) {
|
||||||
allConversations[lastConversationIndex].click();
|
allConversations[lastConversationIndex].click();
|
||||||
} else if (activeConversationIndex < lastConversationIndex) {
|
} else if (activeConversationIndex < lastConversationIndex) {
|
||||||
|
|
|
@ -176,7 +176,9 @@ export default {
|
||||||
'.conversations-list .conversation'
|
'.conversations-list .conversation'
|
||||||
);
|
);
|
||||||
if (hasPressedAltAndMKey(e)) {
|
if (hasPressedAltAndMKey(e)) {
|
||||||
this.$refs.arrowDownButton.$el.click();
|
if (this.$refs.arrowDownButton) {
|
||||||
|
this.$refs.arrowDownButton.$el.click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hasPressedAltAndEKey(e)) {
|
if (hasPressedAltAndEKey(e)) {
|
||||||
const activeConversation = document.querySelector(
|
const activeConversation = document.querySelector(
|
||||||
|
|
|
@ -95,10 +95,15 @@ import AddAccountModal from './sidebarComponents/AddAccountModal.vue';
|
||||||
import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel';
|
import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel';
|
||||||
import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal';
|
import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal';
|
||||||
import {
|
import {
|
||||||
|
hasPressedAltAndCKey,
|
||||||
|
hasPressedAltAndRKey,
|
||||||
|
hasPressedAltAndSKey,
|
||||||
|
hasPressedAltAndVKey,
|
||||||
hasPressedCommandAndForwardSlash,
|
hasPressedCommandAndForwardSlash,
|
||||||
isEscape,
|
isEscape,
|
||||||
} from 'shared/helpers/KeyboardHelpers';
|
} from 'shared/helpers/KeyboardHelpers';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
|
import router from '../../routes';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -276,6 +281,27 @@ export default {
|
||||||
if (isEscape(e)) {
|
if (isEscape(e)) {
|
||||||
this.closeKeyShortcutModal();
|
this.closeKeyShortcutModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasPressedAltAndCKey(e)) {
|
||||||
|
if (!this.isCurrentRouteSameAsNavigation('home')) {
|
||||||
|
router.push({ name: 'home' });
|
||||||
|
}
|
||||||
|
} else if (hasPressedAltAndVKey(e)) {
|
||||||
|
if (!this.isCurrentRouteSameAsNavigation('contacts_dashboard')) {
|
||||||
|
router.push({ name: 'contacts_dashboard' });
|
||||||
|
}
|
||||||
|
} else if (hasPressedAltAndRKey(e)) {
|
||||||
|
if (!this.isCurrentRouteSameAsNavigation('settings_account_reports')) {
|
||||||
|
router.push({ name: 'settings_account_reports' });
|
||||||
|
}
|
||||||
|
} else if (hasPressedAltAndSKey(e)) {
|
||||||
|
if (!this.isCurrentRouteSameAsNavigation('agent_list')) {
|
||||||
|
router.push({ name: 'agent_list' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isCurrentRouteSameAsNavigation(routeName) {
|
||||||
|
return router.currentRoute && router.currentRoute.name === routeName;
|
||||||
},
|
},
|
||||||
toggleSupportChatWindow() {
|
toggleSupportChatWindow() {
|
||||||
window.$chatwoot.toggle();
|
window.$chatwoot.toggle();
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<a href="#" :class="computedChildClass(child)">
|
<a href="#" :class="computedChildClass(child)">
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<i
|
<i
|
||||||
v-if="computedInboxClass(child)"
|
v-if="menuItem.key === 'inbox'"
|
||||||
class="inbox-icon"
|
class="inbox-icon"
|
||||||
:class="computedInboxClass(child)"
|
:class="computedInboxClass(child)"
|
||||||
/>
|
/>
|
||||||
|
@ -59,17 +59,10 @@
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import router from '../../routes';
|
import router from '../../routes';
|
||||||
import {
|
|
||||||
hasPressedAltAndCKey,
|
|
||||||
hasPressedAltAndVKey,
|
|
||||||
hasPressedAltAndRKey,
|
|
||||||
hasPressedAltAndSKey,
|
|
||||||
} from 'shared/helpers/KeyboardHelpers';
|
|
||||||
import adminMixin from '../../mixins/isAdmin';
|
import adminMixin from '../../mixins/isAdmin';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
|
||||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||||
export default {
|
export default {
|
||||||
mixins: [adminMixin, eventListenerMixins],
|
mixins: [adminMixin],
|
||||||
props: {
|
props: {
|
||||||
menuItem: {
|
menuItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -124,20 +117,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleKeyEvents(e) {
|
|
||||||
if (hasPressedAltAndCKey(e)) {
|
|
||||||
router.push({ name: 'home' });
|
|
||||||
}
|
|
||||||
if (hasPressedAltAndVKey(e)) {
|
|
||||||
router.push({ name: 'contacts_dashboard' });
|
|
||||||
}
|
|
||||||
if (hasPressedAltAndRKey(e)) {
|
|
||||||
router.push({ name: 'settings_account_reports' });
|
|
||||||
}
|
|
||||||
if (hasPressedAltAndSKey(e)) {
|
|
||||||
router.push({ name: 'settings_home' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showItem(item) {
|
showItem(item) {
|
||||||
return this.isAdmin && item.newLink !== undefined;
|
return this.isAdmin && item.newLink !== undefined;
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,19 +22,33 @@
|
||||||
src="~dashboard/assets/images/fb-badge.png"
|
src="~dashboard/assets/images/fb-badge.png"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'Channel::TwitterProfile'"
|
v-if="badge === 'twitter-tweet'"
|
||||||
id="badge"
|
id="badge"
|
||||||
class="source-badge"
|
class="source-badge"
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="~dashboard/assets/images/twitter-badge.png"
|
src="~dashboard/assets/images/twitter-badge.png"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'Channel::TwilioSms'"
|
v-if="badge === 'twitter-chat'"
|
||||||
|
id="badge"
|
||||||
|
class="source-badge"
|
||||||
|
:style="badgeStyle"
|
||||||
|
src="~dashboard/assets/images/twitter-chat-badge.png"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-if="badge === 'whatsapp'"
|
||||||
id="badge"
|
id="badge"
|
||||||
class="source-badge"
|
class="source-badge"
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="~dashboard/assets/images/channels/whatsapp.png"
|
src="~dashboard/assets/images/channels/whatsapp.png"
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
v-if="badge === 'sms'"
|
||||||
|
id="badge"
|
||||||
|
class="source-badge"
|
||||||
|
:style="badgeStyle"
|
||||||
|
src="~dashboard/assets/images/channels/sms.png"
|
||||||
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'Channel::Line'"
|
v-if="badge === 'Channel::Line'"
|
||||||
id="badge"
|
id="badge"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
v-if="!hideThumbnail"
|
v-if="!hideThumbnail"
|
||||||
:src="currentContact.thumbnail"
|
:src="currentContact.thumbnail"
|
||||||
:badge="chatMetadata.channel"
|
:badge="inboxBadge"
|
||||||
class="columns"
|
class="columns"
|
||||||
:username="currentContact.name"
|
:username="currentContact.name"
|
||||||
:status="currentContact.availability_status"
|
:status="currentContact.availability_status"
|
||||||
|
@ -68,13 +68,14 @@ import conversationMixin from '../../../mixins/conversations';
|
||||||
import timeMixin from '../../../mixins/time';
|
import timeMixin from '../../../mixins/time';
|
||||||
import router from '../../../routes';
|
import router from '../../../routes';
|
||||||
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
||||||
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [timeMixin, conversationMixin, messageFormatterMixin],
|
mixins: [inboxMixin, timeMixin, conversationMixin, messageFormatterMixin],
|
||||||
props: {
|
props: {
|
||||||
activeLabel: {
|
activeLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -167,14 +168,14 @@ export default {
|
||||||
return this.getPlainText(subject || this.lastMessageInChat.content);
|
return this.getPlainText(subject || this.lastMessageInChat.content);
|
||||||
},
|
},
|
||||||
|
|
||||||
chatInbox() {
|
inbox() {
|
||||||
const { inbox_id: inboxId } = this.chat;
|
const { inbox_id: inboxId } = this.chat;
|
||||||
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
||||||
return stateInbox;
|
return stateInbox;
|
||||||
},
|
},
|
||||||
|
|
||||||
computedInboxClass() {
|
computedInboxClass() {
|
||||||
const { phone_number: phoneNumber, channel_type: type } = this.chatInbox;
|
const { phone_number: phoneNumber, channel_type: type } = this.inbox;
|
||||||
const classByType = getInboxClassByType(type, phoneNumber);
|
const classByType = getInboxClassByType(type, phoneNumber);
|
||||||
return classByType;
|
return classByType;
|
||||||
},
|
},
|
||||||
|
@ -187,11 +188,10 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inboxName() {
|
inboxName() {
|
||||||
const stateInbox = this.chatInbox;
|
const stateInbox = this.inbox;
|
||||||
return stateInbox.name || '';
|
return stateInbox.name || '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
cardClick(chat) {
|
cardClick(chat) {
|
||||||
const { activeInbox } = this;
|
const { activeInbox } = this;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
:src="currentContact.thumbnail"
|
:src="currentContact.thumbnail"
|
||||||
size="40px"
|
size="40px"
|
||||||
:badge="chatMetadata.channel"
|
:badge="inboxBadge"
|
||||||
:username="currentContact.name"
|
:username="currentContact.name"
|
||||||
:status="currentContact.availability_status"
|
:status="currentContact.availability_status"
|
||||||
/>
|
/>
|
||||||
|
@ -42,6 +42,7 @@ import MoreActions from './MoreActions';
|
||||||
import Thumbnail from '../Thumbnail';
|
import Thumbnail from '../Thumbnail';
|
||||||
import agentMixin from '../../../mixins/agentMixin.js';
|
import agentMixin from '../../../mixins/agentMixin.js';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
|
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -49,7 +50,7 @@ export default {
|
||||||
MoreActions,
|
MoreActions,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
},
|
},
|
||||||
mixins: [agentMixin, eventListenerMixins],
|
mixins: [inboxMixin, agentMixin, eventListenerMixins],
|
||||||
props: {
|
props: {
|
||||||
chat: {
|
chat: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -78,6 +79,12 @@ export default {
|
||||||
return this.chat.meta;
|
return this.chat.meta;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inbox() {
|
||||||
|
const { inbox_id: inboxId } = this.chat;
|
||||||
|
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
||||||
|
return stateInbox;
|
||||||
|
},
|
||||||
|
|
||||||
currentContact() {
|
currentContact() {
|
||||||
return this.$store.getters['contacts/getContact'](
|
return this.$store.getters['contacts/getContact'](
|
||||||
this.chat.meta.sender.id
|
this.chat.meta.sender.id
|
||||||
|
|
|
@ -76,7 +76,6 @@
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { mixin as clickaway } from 'vue-clickaway';
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
|
||||||
|
|
||||||
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
||||||
import CannedResponse from './CannedResponse';
|
import CannedResponse from './CannedResponse';
|
||||||
|
@ -108,13 +107,7 @@ export default {
|
||||||
ReplyBottomPanel,
|
ReplyBottomPanel,
|
||||||
WootMessageEditor,
|
WootMessageEditor,
|
||||||
},
|
},
|
||||||
mixins: [
|
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
|
||||||
clickaway,
|
|
||||||
inboxMixin,
|
|
||||||
uiSettingsMixin,
|
|
||||||
alertMixin,
|
|
||||||
eventListenerMixins,
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
selectedTweet: {
|
selectedTweet: {
|
||||||
type: [Object, String],
|
type: [Object, String],
|
||||||
|
@ -304,6 +297,15 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
// Donot use the keyboard listener mixin here as the events here are supposed to be
|
||||||
|
// working even if input/textarea is focussed.
|
||||||
|
document.addEventListener('keydown', this.handleKeyEvents);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
document.removeEventListener('keydown', this.handleKeyEvents);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleUserMention(currentMentionState) {
|
toggleUserMention(currentMentionState) {
|
||||||
this.hasUserMention = currentMentionState;
|
this.hasUserMention = currentMentionState;
|
||||||
|
@ -345,7 +347,10 @@ export default {
|
||||||
await this.$store.dispatch('sendMessage', messagePayload);
|
await this.$store.dispatch('sendMessage', messagePayload);
|
||||||
this.$emit('scrollToMessage');
|
this.$emit('scrollToMessage');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Error
|
const errorMessage =
|
||||||
|
error?.response?.data?.error ||
|
||||||
|
this.$t('CONVERSATION.MESSAGE_ERROR');
|
||||||
|
this.showAlert(errorMessage);
|
||||||
}
|
}
|
||||||
this.hideEmojiPicker();
|
this.hideEmojiPicker();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
'conversation.typing_off': this.onTypingOff,
|
'conversation.typing_off': this.onTypingOff,
|
||||||
'conversation.contact_changed': this.onConversationContactChange,
|
'conversation.contact_changed': this.onConversationContactChange,
|
||||||
'presence.update': this.onPresenceUpdate,
|
'presence.update': this.onPresenceUpdate,
|
||||||
|
'contact.deleted': this.onContactDelete,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +116,14 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
fetchConversationStats = () => {
|
fetchConversationStats = () => {
|
||||||
bus.$emit('fetch_conversation_stats');
|
bus.$emit('fetch_conversation_stats');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onContactDelete = data => {
|
||||||
|
this.app.$store.dispatch(
|
||||||
|
'contacts/deleteContactThroughConversations',
|
||||||
|
data.id
|
||||||
|
);
|
||||||
|
this.fetchConversationStats();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -22,7 +22,10 @@ export const getInboxClassByType = (type, phoneNumber) => {
|
||||||
case INBOX_TYPES.EMAIL:
|
case INBOX_TYPES.EMAIL:
|
||||||
return 'ion-ios-email';
|
return 'ion-ios-email';
|
||||||
|
|
||||||
|
case INBOX_TYPES.TELEGRAM:
|
||||||
|
return 'ion-ios-navigate';
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return 'ion-ios-chatbubble';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Vyberte časové pásmo",
|
"TIMEZONE_LABEL": "Vyberte časové pásmo",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "Vi er ikke tilgængelige i øjeblikket. Skriv en besked og vi svarer, når vi er tilbage.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "Vi er ikke tilgængelige i øjeblikket. Skriv en besked og vi svarer, når vi er tilbage.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -54,6 +54,35 @@
|
||||||
"TITLE": "Create new contact",
|
"TITLE": "Create new contact",
|
||||||
"DESC": "Add basic information details about the contact."
|
"DESC": "Add basic information details about the contact."
|
||||||
},
|
},
|
||||||
|
"IMPORT_CONTACTS": {
|
||||||
|
"BUTTON_LABEL": "Import",
|
||||||
|
"TITLE": "Import Contacts",
|
||||||
|
"DESC": "Import contacts through a CSV file.",
|
||||||
|
"DOWNLOAD_LABEL": "Download a sample csv.",
|
||||||
|
"FORM": {
|
||||||
|
"LABEL": "CSV File",
|
||||||
|
"SUBMIT": "Import",
|
||||||
|
"CANCEL": "Cancel"
|
||||||
|
},
|
||||||
|
"SUCCESS_MESSAGE": "Contacts saved successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error, please try again"
|
||||||
|
},
|
||||||
|
"DELETE_CONTACT": {
|
||||||
|
"BUTTON_LABEL": "Delete Contact",
|
||||||
|
"TITLE": "Delete contact",
|
||||||
|
"DESC": "Delete contact details",
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "Confirm Deletion",
|
||||||
|
"MESSAGE": "Are you sure to delete ",
|
||||||
|
"PLACE_HOLDER": "Please type {contactName} to confirm",
|
||||||
|
"YES": "Yes, Delete ",
|
||||||
|
"NO": "No, Keep "
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Contact deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not delete contact. Please try again later."
|
||||||
|
}
|
||||||
|
},
|
||||||
"CONTACT_FORM": {
|
"CONTACT_FORM": {
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SUBMIT": "Submit",
|
"SUBMIT": "Submit",
|
||||||
|
@ -239,4 +268,4 @@
|
||||||
"ERROR_MESSAGE": "Could not merge contcts, try again!"
|
"ERROR_MESSAGE": "Could not merge contcts, try again!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -84,6 +84,7 @@
|
||||||
"CHANGE_AGENT": "Conversation Assignee changed",
|
"CHANGE_AGENT": "Conversation Assignee changed",
|
||||||
"CHANGE_TEAM": "Conversation team changed",
|
"CHANGE_TEAM": "Conversation team changed",
|
||||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
||||||
|
"MESSAGE_ERROR": "Unable to send this message, please try again later",
|
||||||
"SENT_BY": "Sent by:",
|
"SENT_BY": "Sent by:",
|
||||||
"ASSIGNMENT": {
|
"ASSIGNMENT": {
|
||||||
"SELECT_AGENT": "Select Agent",
|
"SELECT_AGENT": "Select Agent",
|
||||||
|
|
|
@ -56,6 +56,11 @@
|
||||||
"CHANNEL_AVATAR": {
|
"CHANNEL_AVATAR": {
|
||||||
"LABEL": "Channel Avatar"
|
"LABEL": "Channel Avatar"
|
||||||
},
|
},
|
||||||
|
"CHANNEL_WEBHOOK_URL": {
|
||||||
|
"LABEL": "Webhook URL",
|
||||||
|
"PLACEHOLDER": "Enter your Webhook URL",
|
||||||
|
"ERROR": "Please enter a valid URL"
|
||||||
|
},
|
||||||
"CHANNEL_DOMAIN": {
|
"CHANNEL_DOMAIN": {
|
||||||
"LABEL": "Website Domain",
|
"LABEL": "Website Domain",
|
||||||
"PLACEHOLDER": "Enter your website domain (eg: acme.com)"
|
"PLACEHOLDER": "Enter your website domain (eg: acme.com)"
|
||||||
|
@ -127,11 +132,11 @@
|
||||||
"ERROR_MESSAGE": "We were not able to authenticate Twilio credentials, please try again"
|
"ERROR_MESSAGE": "We were not able to authenticate Twilio credentials, please try again"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SMS": {
|
"SMS": {
|
||||||
"TITLE": "SMS Channel via Twilio",
|
"TITLE": "SMS Channel via Twilio",
|
||||||
"DESC": "Start supporting your customers via SMS with Twilio integration."
|
"DESC": "Start supporting your customers via SMS with Twilio integration."
|
||||||
},
|
},
|
||||||
"WHATSAPP": {
|
"WHATSAPP": {
|
||||||
"TITLE": "Whatsapp Channel via Twilio",
|
"TITLE": "Whatsapp Channel via Twilio",
|
||||||
"DESC": "Start supporting your customers via Whatsapp with Twilio integration."
|
"DESC": "Start supporting your customers via Whatsapp with Twilio integration."
|
||||||
},
|
},
|
||||||
|
@ -195,6 +200,10 @@
|
||||||
"SUBMIT_BUTTON": "Create LINE Channel",
|
"SUBMIT_BUTTON": "Create LINE Channel",
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR_MESSAGE": "We were not able to save the LINE channel"
|
"ERROR_MESSAGE": "We were not able to save the LINE channel"
|
||||||
|
},
|
||||||
|
"API_CALLBACK": {
|
||||||
|
"TITLE": "Callback URL",
|
||||||
|
"SUBTITLE": "You have to configure the webhook URL in LINE application with the URL mentioned here."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TELEGRAM_CHANNEL": {
|
"TELEGRAM_CHANNEL": {
|
||||||
|
@ -350,7 +359,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -13,18 +13,18 @@
|
||||||
"STATUS_TABS": [
|
"STATUS_TABS": [
|
||||||
{
|
{
|
||||||
"NAME": "Apri",
|
"NAME": "Apri",
|
||||||
"KEY": "contaaperture"
|
"KEY": "openCount"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "Risolti",
|
"NAME": "Risolti",
|
||||||
"KEY": "Conteggio"
|
"KEY": "allConvCount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ASSIGNEE_TYPE_TABS": [
|
"ASSIGNEE_TYPE_TABS": [
|
||||||
{
|
{
|
||||||
"NAME": "Miniera",
|
"NAME": "Miniera",
|
||||||
"KEY": "Io",
|
"KEY": "Io",
|
||||||
"COUNT_KEY": "contaMinore"
|
"COUNT_KEY": "mineCount"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "Non assegnato",
|
"NAME": "Non assegnato",
|
||||||
|
@ -40,11 +40,11 @@
|
||||||
"CHAT_STATUS_ITEMS": [
|
"CHAT_STATUS_ITEMS": [
|
||||||
{
|
{
|
||||||
"TEXT": "Apri",
|
"TEXT": "Apri",
|
||||||
"VALUE": "Aperto"
|
"VALUE": "open"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"TEXT": "Risolti",
|
"TEXT": "Risolti",
|
||||||
"VALUE": "risolto"
|
"VALUE": "resolved"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"TEXT": "Pending",
|
"TEXT": "Pending",
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
"TIMEZONE_LABEL": "Select timezone",
|
"TIMEZONE_LABEL": "Select timezone",
|
||||||
"UPDATE": "Update business hours settings",
|
"UPDATE": "Update business hours settings",
|
||||||
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
"TOGGLE_AVAILABILITY": "Enable business availability for this inbox",
|
||||||
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for vistors",
|
"UNAVAILABLE_MESSAGE_LABEL": "Unavailable message for visitors",
|
||||||
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
"UNAVAILABLE_MESSAGE_DEFAULT": "We are unavailable at the moment. Leave a message we will respond once we are back.",
|
||||||
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
"TOGGLE_HELP": "Enabling business availability will show the available hours on live chat widget even if all the agents are offline. Outside available hours vistors can be warned with a message and a pre-chat form.",
|
||||||
"DAY": {
|
"DAY": {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<span class="close-button" @click="onClose">
|
<span class="close-button" @click="onClose">
|
||||||
<i class="ion-android-close close-icon" />
|
<i class="ion-android-close close-icon" />
|
||||||
</span>
|
</span>
|
||||||
<contact-info show-new-message :contact="contact" />
|
<contact-info show-new-message :contact="contact" @panel-close="onClose" />
|
||||||
<accordion-item
|
<accordion-item
|
||||||
:title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CUSTOM_ATTRIBUTES')"
|
:title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CUSTOM_ATTRIBUTES')"
|
||||||
:is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
|
:is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
|
||||||
|
|
|
@ -24,12 +24,12 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
savedLabels() {
|
savedLabels() {
|
||||||
const result = this.$store.getters['contactLabels/getContactLabels'](
|
const availableContactLabels = this.$store.getters[
|
||||||
this.contactId
|
'contactLabels/getContactLabels'
|
||||||
|
](this.contactId);
|
||||||
|
return this.allLabels.filter(({ title }) =>
|
||||||
|
availableContactLabels.includes(title)
|
||||||
);
|
);
|
||||||
return result.map(value => {
|
|
||||||
return this.allLabels.find(label => label.title === value);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
this-selected-contact-id=""
|
this-selected-contact-id=""
|
||||||
:on-input-search="onInputSearch"
|
:on-input-search="onInputSearch"
|
||||||
:on-toggle-create="onToggleCreate"
|
:on-toggle-create="onToggleCreate"
|
||||||
|
:on-toggle-import="onToggleImport"
|
||||||
:header-title="label"
|
:header-title="label"
|
||||||
/>
|
/>
|
||||||
<contacts-table
|
<contacts-table
|
||||||
|
@ -30,6 +31,9 @@
|
||||||
:on-close="closeContactInfoPanel"
|
:on-close="closeContactInfoPanel"
|
||||||
/>
|
/>
|
||||||
<create-contact :show="showCreateModal" @cancel="onToggleCreate" />
|
<create-contact :show="showCreateModal" @cancel="onToggleCreate" />
|
||||||
|
<woot-modal :show.sync="showImportModal" :on-close="onToggleImport">
|
||||||
|
<import-contacts v-if="showImportModal" :on-close="onToggleImport" />
|
||||||
|
</woot-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -41,6 +45,7 @@ import ContactsTable from './ContactsTable';
|
||||||
import ContactInfoPanel from './ContactInfoPanel';
|
import ContactInfoPanel from './ContactInfoPanel';
|
||||||
import CreateContact from 'dashboard/routes/dashboard/conversation/contact/CreateContact';
|
import CreateContact from 'dashboard/routes/dashboard/conversation/contact/CreateContact';
|
||||||
import TableFooter from 'dashboard/components/widgets/TableFooter';
|
import TableFooter from 'dashboard/components/widgets/TableFooter';
|
||||||
|
import ImportContacts from './ImportContacts.vue';
|
||||||
|
|
||||||
const DEFAULT_PAGE = 1;
|
const DEFAULT_PAGE = 1;
|
||||||
|
|
||||||
|
@ -51,6 +56,7 @@ export default {
|
||||||
TableFooter,
|
TableFooter,
|
||||||
ContactInfoPanel,
|
ContactInfoPanel,
|
||||||
CreateContact,
|
CreateContact,
|
||||||
|
ImportContacts,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
label: { type: String, default: '' },
|
label: { type: String, default: '' },
|
||||||
|
@ -59,6 +65,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
showCreateModal: false,
|
showCreateModal: false,
|
||||||
|
showImportModal: false,
|
||||||
selectedContactId: '',
|
selectedContactId: '',
|
||||||
sortConfig: { name: 'asc' },
|
sortConfig: { name: 'asc' },
|
||||||
};
|
};
|
||||||
|
@ -168,6 +175,9 @@ export default {
|
||||||
onToggleCreate() {
|
onToggleCreate() {
|
||||||
this.showCreateModal = !this.showCreateModal;
|
this.showCreateModal = !this.showCreateModal;
|
||||||
},
|
},
|
||||||
|
onToggleImport() {
|
||||||
|
this.showImportModal = !this.showImportModal;
|
||||||
|
},
|
||||||
onSortChange(params) {
|
onSortChange(params) {
|
||||||
this.sortConfig = params;
|
this.sortConfig = params;
|
||||||
this.fetchContacts(this.meta.currentPage);
|
this.fetchContacts(this.meta.currentPage);
|
||||||
|
|
|
@ -29,11 +29,20 @@
|
||||||
<woot-button
|
<woot-button
|
||||||
color-scheme="success"
|
color-scheme="success"
|
||||||
icon="ion-android-add-circle"
|
icon="ion-android-add-circle"
|
||||||
@click="onToggleCreate"
|
class="margin-right-small"
|
||||||
data-testid="create-new-contact"
|
data-testid="create-new-contact"
|
||||||
|
@click="onToggleCreate"
|
||||||
>
|
>
|
||||||
{{ $t('CREATE_CONTACT.BUTTON_LABEL') }}
|
{{ $t('CREATE_CONTACT.BUTTON_LABEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
|
|
||||||
|
<woot-button
|
||||||
|
color-scheme="info"
|
||||||
|
icon="ion-android-upload"
|
||||||
|
@click="onToggleImport"
|
||||||
|
>
|
||||||
|
{{ $t('IMPORT_CONTACTS.BUTTON_LABEL') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -41,7 +50,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
components: {},
|
|
||||||
props: {
|
props: {
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -63,10 +71,15 @@ export default {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
onToggleImport: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showCreateModal: false,
|
showCreateModal: false,
|
||||||
|
showImportModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -78,6 +91,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import '~dashboard/assets/scss/_utility-helpers.scss';
|
||||||
.page-title {
|
.page-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<modal :show.sync="show" :on-close="onClose">
|
||||||
|
<div class="column content-box">
|
||||||
|
<woot-modal-header :header-title="$t('IMPORT_CONTACTS.TITLE')">
|
||||||
|
<p>
|
||||||
|
{{ $t('IMPORT_CONTACTS.DESC') }}
|
||||||
|
<a :href="csvUrl" download="import-contacts-sample">{{
|
||||||
|
$t('IMPORT_CONTACTS.DOWNLOAD_LABEL')
|
||||||
|
}}</a>
|
||||||
|
</p>
|
||||||
|
</woot-modal-header>
|
||||||
|
<div class="row modal-content">
|
||||||
|
<div class="medium-12 columns">
|
||||||
|
<label>
|
||||||
|
<span>{{ $t('IMPORT_CONTACTS.FORM.LABEL') }}</span>
|
||||||
|
<input
|
||||||
|
id="file"
|
||||||
|
ref="file"
|
||||||
|
type="file"
|
||||||
|
accept="text/csv"
|
||||||
|
@change="handleFileUpload"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="medium-12 columns">
|
||||||
|
<woot-button
|
||||||
|
:disabled="uiFlags.isCreating || !file"
|
||||||
|
:loading="uiFlags.isCreating"
|
||||||
|
@click="uploadFile"
|
||||||
|
>
|
||||||
|
{{ $t('IMPORT_CONTACTS.FORM.SUBMIT') }}
|
||||||
|
</woot-button>
|
||||||
|
<button class="button clear" @click.prevent="onClose">
|
||||||
|
{{ $t('IMPORT_CONTACTS.FORM.CANCEL') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Modal from '../../../../components/Modal';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
mixins: [alertMixin],
|
||||||
|
props: {
|
||||||
|
onClose: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
file: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
uiFlags: 'contacts/getUIFlags',
|
||||||
|
}),
|
||||||
|
csvUrl() {
|
||||||
|
return '/downloads/import-contacts-sample.csv';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async uploadFile() {
|
||||||
|
try {
|
||||||
|
if (!this.file) return;
|
||||||
|
await this.$store.dispatch('contacts/import', this.file);
|
||||||
|
this.onClose();
|
||||||
|
this.showAlert(this.$t('IMPORT_CONTACTS.SUCCESS_MESSAGE'));
|
||||||
|
} catch (error) {
|
||||||
|
this.showAlert(
|
||||||
|
error.message || this.$t('IMPORT_CONTACTS.ERROR_MESSAGE')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleFileUpload() {
|
||||||
|
this.file = this.$refs.file.files[0];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -48,30 +48,59 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<div v-if="!showNewMessage">
|
||||||
v-if="!showNewMessage"
|
<div>
|
||||||
class="edit-contact"
|
<woot-button
|
||||||
variant="link"
|
class="edit-contact"
|
||||||
size="small"
|
variant="link"
|
||||||
@click="toggleEditModal"
|
size="small"
|
||||||
>
|
@click="toggleEditModal"
|
||||||
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
>
|
||||||
</woot-button>
|
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
||||||
<div v-else class="contact-actions">
|
</woot-button>
|
||||||
<woot-button
|
</div>
|
||||||
class="new-message"
|
<div v-if="isAdmin">
|
||||||
size="small expanded"
|
<woot-button
|
||||||
@click="toggleConversationModal"
|
class="delete-contact"
|
||||||
>
|
variant="link"
|
||||||
{{ $t('CONTACT_PANEL.NEW_MESSAGE') }}
|
size="small"
|
||||||
</woot-button>
|
color-scheme="alert"
|
||||||
<woot-button
|
@click="toggleDeleteModal"
|
||||||
variant="smooth"
|
:disabled="uiFlags.isDeleting"
|
||||||
size="small expanded"
|
>
|
||||||
@click="toggleEditModal"
|
{{ $t('DELETE_CONTACT.BUTTON_LABEL') }}
|
||||||
>
|
</woot-button>
|
||||||
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
</div>
|
||||||
</woot-button>
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="contact-actions">
|
||||||
|
<woot-button
|
||||||
|
v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
||||||
|
class="new-message"
|
||||||
|
icon="ion-chatboxes"
|
||||||
|
size="small expanded"
|
||||||
|
@click="toggleConversationModal"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-tooltip="$t('EDIT_CONTACT.BUTTON_LABEL')"
|
||||||
|
class="edit-contact"
|
||||||
|
icon="ion-edit"
|
||||||
|
variant="smooth"
|
||||||
|
size="small expanded"
|
||||||
|
@click="toggleEditModal"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-if="isAdmin"
|
||||||
|
v-tooltip="$t('DELETE_CONTACT.BUTTON_LABEL')"
|
||||||
|
class="delete-contact"
|
||||||
|
icon="ion-trash-a"
|
||||||
|
variant="hollow"
|
||||||
|
size="small expanded"
|
||||||
|
color-scheme="alert"
|
||||||
|
@click="toggleDeleteModal"
|
||||||
|
:disabled="uiFlags.isDeleting"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<edit-contact
|
<edit-contact
|
||||||
v-if="showEditModal"
|
v-if="showEditModal"
|
||||||
|
@ -80,11 +109,24 @@
|
||||||
@cancel="toggleEditModal"
|
@cancel="toggleEditModal"
|
||||||
/>
|
/>
|
||||||
<new-conversation
|
<new-conversation
|
||||||
|
v-if="contact.id"
|
||||||
:show="showConversationModal"
|
:show="showConversationModal"
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
@cancel="toggleConversationModal"
|
@cancel="toggleConversationModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<woot-confirm-delete-modal
|
||||||
|
v-if="showDeleteModal"
|
||||||
|
:show.sync="showDeleteModal"
|
||||||
|
:title="$t('DELETE_CONTACT.CONFIRM.TITLE')"
|
||||||
|
:message="confirmDeleteMessage"
|
||||||
|
:confirm-text="deleteConfirmText"
|
||||||
|
:reject-text="deleteRejectText"
|
||||||
|
:confirm-value="contact.name"
|
||||||
|
:confirm-place-holder-text="confirmPlaceHolderText"
|
||||||
|
@on-confirm="confirmDeletion"
|
||||||
|
@on-close="closeDelete"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -93,6 +135,9 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
import SocialIcons from './SocialIcons';
|
import SocialIcons from './SocialIcons';
|
||||||
import EditContact from './EditContact';
|
import EditContact from './EditContact';
|
||||||
import NewConversation from './NewConversation';
|
import NewConversation from './NewConversation';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import adminMixin from '../../../../mixins/isAdmin';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -102,6 +147,7 @@ export default {
|
||||||
SocialIcons,
|
SocialIcons,
|
||||||
NewConversation,
|
NewConversation,
|
||||||
},
|
},
|
||||||
|
mixins: [alertMixin, adminMixin],
|
||||||
props: {
|
props: {
|
||||||
contact: {
|
contact: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -120,9 +166,11 @@ export default {
|
||||||
return {
|
return {
|
||||||
showEditModal: false,
|
showEditModal: false,
|
||||||
showConversationModal: false,
|
showConversationModal: false,
|
||||||
|
showDeleteModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters({ uiFlags: 'contacts/getUIFlags' }),
|
||||||
additionalAttributes() {
|
additionalAttributes() {
|
||||||
return this.contact.additional_attributes || {};
|
return this.contact.additional_attributes || {};
|
||||||
},
|
},
|
||||||
|
@ -134,6 +182,23 @@ export default {
|
||||||
|
|
||||||
return { twitter: twitterScreenName, ...(socialProfiles || {}) };
|
return { twitter: twitterScreenName, ...(socialProfiles || {}) };
|
||||||
},
|
},
|
||||||
|
// Delete Modal
|
||||||
|
deleteConfirmText() {
|
||||||
|
return `${this.$t('DELETE_CONTACT.CONFIRM.YES')} ${this.contact.name}`;
|
||||||
|
},
|
||||||
|
deleteRejectText() {
|
||||||
|
return `${this.$t('DELETE_CONTACT.CONFIRM.NO')} ${this.contact.name}`;
|
||||||
|
},
|
||||||
|
confirmDeleteMessage() {
|
||||||
|
return `${this.$t('DELETE_CONTACT.CONFIRM.MESSAGE')} ${
|
||||||
|
this.contact.name
|
||||||
|
} ?`;
|
||||||
|
},
|
||||||
|
confirmPlaceHolderText() {
|
||||||
|
return `${this.$t('DELETE_CONTACT.CONFIRM.PLACE_HOLDER', {
|
||||||
|
contactName: this.contact.name,
|
||||||
|
})}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleEditModal() {
|
toggleEditModal() {
|
||||||
|
@ -142,6 +207,31 @@ export default {
|
||||||
toggleConversationModal() {
|
toggleConversationModal() {
|
||||||
this.showConversationModal = !this.showConversationModal;
|
this.showConversationModal = !this.showConversationModal;
|
||||||
},
|
},
|
||||||
|
toggleDeleteModal() {
|
||||||
|
this.showDeleteModal = !this.showDeleteModal;
|
||||||
|
},
|
||||||
|
confirmDeletion() {
|
||||||
|
this.deleteContact(this.contact);
|
||||||
|
this.closeDelete();
|
||||||
|
},
|
||||||
|
closeDelete() {
|
||||||
|
this.showDeleteModal = false;
|
||||||
|
this.showConversationModal = false;
|
||||||
|
this.showEditModal = false;
|
||||||
|
},
|
||||||
|
async deleteContact({ id }) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('contacts/delete', id);
|
||||||
|
this.$emit('panel-close');
|
||||||
|
this.showAlert(this.$t('DELETE_CONTACT.API.SUCCESS_MESSAGE'));
|
||||||
|
} catch (error) {
|
||||||
|
this.showAlert(
|
||||||
|
error.message
|
||||||
|
? error.message
|
||||||
|
: this.$t('DELETE_CONTACT.API.ERROR_MESSAGE')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -179,17 +269,32 @@ export default {
|
||||||
.contact-actions {
|
.contact-actions {
|
||||||
margin-top: var(--space-small);
|
margin-top: var(--space-small);
|
||||||
}
|
}
|
||||||
.button.edit-contact {
|
|
||||||
|
.edit-contact {
|
||||||
margin-left: var(--space-medium);
|
margin-left: var(--space-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.new-message {
|
.delete-contact {
|
||||||
margin-right: var(--space-small);
|
margin-left: var(--space-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-actions {
|
.contact-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
.new-message {
|
||||||
|
font-size: var(--font-size-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-contact {
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
font-size: var(--font-size-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-contact {
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
font-size: var(--font-size-medium);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -237,7 +237,7 @@ export default {
|
||||||
if (this.isOngoingType) {
|
if (this.isOngoingType) {
|
||||||
return this.$store.getters['inboxes/getWebsiteInboxes'];
|
return this.$store.getters['inboxes/getWebsiteInboxes'];
|
||||||
}
|
}
|
||||||
return this.$store.getters['inboxes/getTwilioInboxes'];
|
return this.$store.getters['inboxes/getTwilioSMSInboxes'];
|
||||||
},
|
},
|
||||||
sendersAndBotList() {
|
sendersAndBotList() {
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -161,7 +161,7 @@ export default {
|
||||||
if (this.isOngoingType) {
|
if (this.isOngoingType) {
|
||||||
return this.$store.getters['inboxes/getWebsiteInboxes'];
|
return this.$store.getters['inboxes/getWebsiteInboxes'];
|
||||||
}
|
}
|
||||||
return this.$store.getters['inboxes/getTwilioInboxes'];
|
return this.$store.getters['inboxes/getTwilioSMSInboxes'];
|
||||||
},
|
},
|
||||||
pageTitle() {
|
pageTitle() {
|
||||||
return `${this.$t('CAMPAIGN.EDIT.TITLE')} - ${
|
return `${this.$t('CAMPAIGN.EDIT.TITLE')} - ${
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default {
|
||||||
const selectedAgents = this.selectedAgents.map(x => x.id);
|
const selectedAgents = this.selectedAgents.map(x => x.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await InboxMembersAPI.create({ inboxId, agentList: selectedAgents });
|
await InboxMembersAPI.update({ inboxId, agentList: selectedAgents });
|
||||||
router.replace({
|
router.replace({
|
||||||
name: 'settings_inbox_finish',
|
name: 'settings_inbox_finish',
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<woot-code
|
<woot-code
|
||||||
v-if="isATwilioInbox"
|
v-if="isATwilioInbox"
|
||||||
lang="html"
|
lang="html"
|
||||||
:script="currentInbox.webhook_url"
|
:script="currentInbox.callback_webhook_url"
|
||||||
>
|
>
|
||||||
</woot-code>
|
</woot-code>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<woot-code
|
<woot-code
|
||||||
v-if="isALineInbox"
|
v-if="isALineInbox"
|
||||||
lang="html"
|
lang="html"
|
||||||
:script="currentInbox.webhook_url"
|
:script="currentInbox.callback_webhook_url"
|
||||||
>
|
>
|
||||||
</woot-code>
|
</woot-code>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,6 +93,12 @@ export default {
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isALineInbox) {
|
||||||
|
return `${this.$t('INBOX_MGMT.FINISH.MESSAGE')}. ${this.$t(
|
||||||
|
'INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.SUBTITLE'
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isAEmailInbox) {
|
if (this.isAEmailInbox) {
|
||||||
return this.$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.FINISH_MESSAGE');
|
return this.$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.FINISH_MESSAGE');
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,9 @@
|
||||||
<span v-if="item.channel_type === 'Channel::Telegram'">
|
<span v-if="item.channel_type === 'Channel::Telegram'">
|
||||||
Telegram
|
Telegram
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="item.channel_type === 'Channel::Line'">
|
||||||
|
Line
|
||||||
|
</span>
|
||||||
<span v-if="item.channel_type === 'Channel::Api'">
|
<span v-if="item.channel_type === 'Channel::Api'">
|
||||||
{{ globalConfig.apiChannelName || 'API' }}
|
{{ globalConfig.apiChannelName || 'API' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<woot-avatar-uploader
|
<woot-avatar-uploader
|
||||||
:label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL')"
|
:label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL')"
|
||||||
:src="avatarUrl"
|
:src="avatarUrl"
|
||||||
deleteAvatar
|
delete-avatar
|
||||||
@change="handleImageUpload"
|
@change="handleImageUpload"
|
||||||
@onAvatarDelete="handleAvatarDelete"
|
@onAvatarDelete="handleAvatarDelete"
|
||||||
/>
|
/>
|
||||||
|
@ -32,6 +32,24 @@
|
||||||
:label="inboxNameLabel"
|
:label="inboxNameLabel"
|
||||||
:placeholder="inboxNamePlaceHolder"
|
:placeholder="inboxNamePlaceHolder"
|
||||||
/>
|
/>
|
||||||
|
<woot-input
|
||||||
|
v-if="isAPIInbox"
|
||||||
|
v-model.trim="webhookUrl"
|
||||||
|
class="medium-9 columns"
|
||||||
|
:class="{ error: $v.webhookUrl.$error }"
|
||||||
|
:label="
|
||||||
|
$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.LABEL')
|
||||||
|
"
|
||||||
|
:placeholder="
|
||||||
|
$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.PLACEHOLDER')
|
||||||
|
"
|
||||||
|
:error="
|
||||||
|
$v.webhookUrl.$error
|
||||||
|
? $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_URL.ERROR')
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
@blur="$v.webhookUrl.$touch"
|
||||||
|
/>
|
||||||
<woot-input
|
<woot-input
|
||||||
v-if="isAWebWidgetInbox"
|
v-if="isAWebWidgetInbox"
|
||||||
v-model.trim="channelWebsiteUrl"
|
v-model.trim="channelWebsiteUrl"
|
||||||
|
@ -212,6 +230,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<woot-submit-button
|
<woot-submit-button
|
||||||
|
v-if="isAPIInbox"
|
||||||
|
type="submit"
|
||||||
|
:disabled="$v.webhookUrl.$invalid"
|
||||||
|
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
||||||
|
:loading="uiFlags.isUpdatingInbox"
|
||||||
|
@click="updateInbox"
|
||||||
|
/>
|
||||||
|
<woot-submit-button
|
||||||
|
v-else
|
||||||
|
type="submit"
|
||||||
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
||||||
:loading="uiFlags.isUpdatingInbox"
|
:loading="uiFlags.isUpdatingInbox"
|
||||||
@click="updateInbox"
|
@click="updateInbox"
|
||||||
|
@ -259,7 +287,21 @@
|
||||||
:title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
|
:title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
|
||||||
:sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')"
|
:sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')"
|
||||||
>
|
>
|
||||||
<woot-code :script="twilioCallbackURL" lang="html"></woot-code>
|
<woot-code
|
||||||
|
:script="inbox.callback_webhook_url"
|
||||||
|
lang="html"
|
||||||
|
></woot-code>
|
||||||
|
</settings-section>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isALineChannel" class="settings--content">
|
||||||
|
<settings-section
|
||||||
|
:title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.TITLE')"
|
||||||
|
:sub-title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.SUBTITLE')"
|
||||||
|
>
|
||||||
|
<woot-code
|
||||||
|
:script="inbox.callback_webhook_url"
|
||||||
|
lang="html"
|
||||||
|
></woot-code>
|
||||||
</settings-section>
|
</settings-section>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isAWebWidgetInbox">
|
<div v-else-if="isAWebWidgetInbox">
|
||||||
|
@ -310,6 +352,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { createMessengerScript } from 'dashboard/helper/scriptGenerator';
|
import { createMessengerScript } from 'dashboard/helper/scriptGenerator';
|
||||||
|
import { required } from 'vuelidate/lib/validators';
|
||||||
|
import { shouldBeUrl } from 'shared/helpers/Validators';
|
||||||
import configMixin from 'shared/mixins/configMixin';
|
import configMixin from 'shared/mixins/configMixin';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import SettingIntroBanner from 'dashboard/components/widgets/SettingIntroBanner';
|
import SettingIntroBanner from 'dashboard/components/widgets/SettingIntroBanner';
|
||||||
|
@ -343,6 +387,7 @@ export default {
|
||||||
csatSurveyEnabled: false,
|
csatSurveyEnabled: false,
|
||||||
selectedInboxName: '',
|
selectedInboxName: '',
|
||||||
channelWebsiteUrl: '',
|
channelWebsiteUrl: '',
|
||||||
|
webhookUrl: '',
|
||||||
channelWelcomeTitle: '',
|
channelWelcomeTitle: '',
|
||||||
channelWelcomeTagline: '',
|
channelWelcomeTagline: '',
|
||||||
selectedFeatureFlags: [],
|
selectedFeatureFlags: [],
|
||||||
|
@ -378,6 +423,10 @@ export default {
|
||||||
key: 'collaborators',
|
key: 'collaborators',
|
||||||
name: this.$t('INBOX_MGMT.TABS.COLLABORATORS'),
|
name: this.$t('INBOX_MGMT.TABS.COLLABORATORS'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'businesshours',
|
||||||
|
name: this.$t('INBOX_MGMT.TABS.BUSINESS_HOURS'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.isAWebWidgetInbox) {
|
if (this.isAWebWidgetInbox) {
|
||||||
|
@ -387,10 +436,6 @@ export default {
|
||||||
key: 'preChatForm',
|
key: 'preChatForm',
|
||||||
name: this.$t('INBOX_MGMT.TABS.PRE_CHAT_FORM'),
|
name: this.$t('INBOX_MGMT.TABS.PRE_CHAT_FORM'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'businesshours',
|
|
||||||
name: this.$t('INBOX_MGMT.TABS.BUSINESS_HOURS'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'configuration',
|
key: 'configuration',
|
||||||
name: this.$t('INBOX_MGMT.TABS.CONFIGURATION'),
|
name: this.$t('INBOX_MGMT.TABS.CONFIGURATION'),
|
||||||
|
@ -398,7 +443,12 @@ export default {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isATwilioChannel || this.isAPIInbox || this.isAnEmailChannel) {
|
if (
|
||||||
|
this.isATwilioChannel ||
|
||||||
|
this.isALineChannel ||
|
||||||
|
this.isAPIInbox ||
|
||||||
|
this.isAnEmailChannel
|
||||||
|
) {
|
||||||
return [
|
return [
|
||||||
...visibleToAllChannelTabs,
|
...visibleToAllChannelTabs,
|
||||||
{
|
{
|
||||||
|
@ -484,6 +534,7 @@ export default {
|
||||||
this.fetchAttachedAgents();
|
this.fetchAttachedAgents();
|
||||||
this.avatarUrl = this.inbox.avatar_url;
|
this.avatarUrl = this.inbox.avatar_url;
|
||||||
this.selectedInboxName = this.inbox.name;
|
this.selectedInboxName = this.inbox.name;
|
||||||
|
this.webhookUrl = this.inbox.webhook_url;
|
||||||
this.greetingEnabled = this.inbox.greeting_enabled || false;
|
this.greetingEnabled = this.inbox.greeting_enabled || false;
|
||||||
this.greetingMessage = this.inbox.greeting_message || '';
|
this.greetingMessage = this.inbox.greeting_message || '';
|
||||||
this.autoAssignment = this.inbox.enable_auto_assignment;
|
this.autoAssignment = this.inbox.enable_auto_assignment;
|
||||||
|
@ -536,6 +587,7 @@ export default {
|
||||||
channel: {
|
channel: {
|
||||||
widget_color: this.inbox.widget_color,
|
widget_color: this.inbox.widget_color,
|
||||||
website_url: this.channelWebsiteUrl,
|
website_url: this.channelWebsiteUrl,
|
||||||
|
webhook_url: this.webhookUrl,
|
||||||
welcome_title: this.channelWelcomeTitle || '',
|
welcome_title: this.channelWelcomeTitle || '',
|
||||||
welcome_tagline: this.channelWelcomeTagline || '',
|
welcome_tagline: this.channelWelcomeTagline || '',
|
||||||
selectedFeatureFlags: this.selectedFeatureFlags,
|
selectedFeatureFlags: this.selectedFeatureFlags,
|
||||||
|
@ -574,6 +626,10 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
|
webhookUrl: {
|
||||||
|
required,
|
||||||
|
shouldBeUrl,
|
||||||
|
},
|
||||||
selectedAgents: {
|
selectedAgents: {
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return !!this.selectedAgents.length;
|
return !!this.selectedAgents.length;
|
||||||
|
|
|
@ -82,6 +82,9 @@ export const mutations = {
|
||||||
const conversations = $state.records[id] || [];
|
const conversations = $state.records[id] || [];
|
||||||
Vue.set($state.records, id, [...conversations, data]);
|
Vue.set($state.records, id, [...conversations, data]);
|
||||||
},
|
},
|
||||||
|
[types.default.DELETE_CONTACT_CONVERSATION]: ($state, id) => {
|
||||||
|
Vue.delete($state.records, id);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -82,6 +82,32 @@ export const actions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
import: async ({ commit }, file) => {
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isCreating: true });
|
||||||
|
try {
|
||||||
|
await ContactAPI.importContacts(file);
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isCreating: false });
|
||||||
|
} catch (error) {
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isCreating: false });
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
throw new ExceptionWithMessage(error.response.data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: async ({ commit }, id) => {
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isDeleting: true });
|
||||||
|
try {
|
||||||
|
await ContactAPI.delete(id);
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isDeleting: false });
|
||||||
|
} catch (error) {
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isDeleting: false });
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
throw new Error(error.response.data.message);
|
||||||
|
} else {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
fetchContactableInbox: async ({ commit }, id) => {
|
fetchContactableInbox: async ({ commit }, id) => {
|
||||||
commit(types.SET_CONTACT_UI_FLAG, { isFetchingInboxes: true });
|
commit(types.SET_CONTACT_UI_FLAG, { isFetchingInboxes: true });
|
||||||
|
@ -110,4 +136,12 @@ export const actions = {
|
||||||
setContact({ commit }, data) {
|
setContact({ commit }, data) {
|
||||||
commit(types.SET_CONTACT_ITEM, data);
|
commit(types.SET_CONTACT_ITEM, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteContactThroughConversations: ({ commit }, id) => {
|
||||||
|
commit(types.DELETE_CONTACT, id);
|
||||||
|
commit(types.CLEAR_CONTACT_CONVERSATIONS, id, { root: true });
|
||||||
|
commit(`contactConversations/${types.DELETE_CONTACT_CONVERSATION}`, id, {
|
||||||
|
root: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ const state = {
|
||||||
isFetchingItem: false,
|
isFetchingItem: false,
|
||||||
isFetchingInboxes: false,
|
isFetchingInboxes: false,
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
|
isDeleting: false,
|
||||||
},
|
},
|
||||||
sortOrder: [],
|
sortOrder: [],
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,6 +46,12 @@ export const mutations = {
|
||||||
Vue.set($state.records, data.id, data);
|
Vue.set($state.records, data.id, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[types.DELETE_CONTACT]: ($state, id) => {
|
||||||
|
const index = $state.sortOrder.findIndex(item => item === id);
|
||||||
|
Vue.delete($state.sortOrder, index);
|
||||||
|
Vue.delete($state.records, id);
|
||||||
|
},
|
||||||
|
|
||||||
[types.UPDATE_CONTACTS_PRESENCE]: ($state, data) => {
|
[types.UPDATE_CONTACTS_PRESENCE]: ($state, data) => {
|
||||||
Object.values($state.records).forEach(element => {
|
Object.values($state.records).forEach(element => {
|
||||||
const availabilityStatus = data[element.id];
|
const availabilityStatus = data[element.id];
|
||||||
|
|
|
@ -155,6 +155,7 @@ const actions = {
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessage: async ({ commit }, data) => {
|
sendMessage: async ({ commit }, data) => {
|
||||||
|
// eslint-disable-next-line no-useless-catch
|
||||||
try {
|
try {
|
||||||
const pendingMessage = createPendingMessage(data);
|
const pendingMessage = createPendingMessage(data);
|
||||||
commit(types.default.ADD_MESSAGE, pendingMessage);
|
commit(types.default.ADD_MESSAGE, pendingMessage);
|
||||||
|
@ -164,7 +165,7 @@ const actions = {
|
||||||
status: MESSAGE_STATUS.SENT,
|
status: MESSAGE_STATUS.SENT,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle error
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -177,6 +177,13 @@ export const mutations = {
|
||||||
Vue.set(chat, 'can_reply', canReply);
|
Vue.set(chat, 'can_reply', canReply);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[types.default.CLEAR_CONTACT_CONVERSATIONS](_state, contactId) {
|
||||||
|
const chats = _state.allConversations.filter(
|
||||||
|
c => c.meta.sender.id !== contactId
|
||||||
|
);
|
||||||
|
Vue.set(_state, 'allConversations', chats);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -73,6 +73,11 @@ export const getters = {
|
||||||
item => item.channel_type === INBOX_TYPES.TWILIO
|
item => item.channel_type === INBOX_TYPES.TWILIO
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
getTwilioSMSInboxes($state) {
|
||||||
|
return $state.records.filter(
|
||||||
|
item => item.channel_type === INBOX_TYPES.TWILIO && item.medium === 'sms'
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
|
|
@ -139,6 +139,27 @@ describe('#actions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#delete', () => {
|
||||||
|
it('sends correct mutations if API is success', async () => {
|
||||||
|
axios.delete.mockResolvedValue();
|
||||||
|
await actions.delete({ commit }, contactList[0].id);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.SET_CONTACT_UI_FLAG, { isDeleting: true }],
|
||||||
|
[types.SET_CONTACT_UI_FLAG, { isDeleting: false }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('sends correct actions if API is error', async () => {
|
||||||
|
axios.delete.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
|
await expect(
|
||||||
|
actions.delete({ commit }, contactList[0].id)
|
||||||
|
).rejects.toThrow(Error);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.SET_CONTACT_UI_FLAG, { isDeleting: true }],
|
||||||
|
[types.SET_CONTACT_UI_FLAG, { isDeleting: false }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#setContact', () => {
|
describe('#setContact', () => {
|
||||||
it('returns correct mutations', () => {
|
it('returns correct mutations', () => {
|
||||||
const data = { id: 1, name: 'john doe', availability_status: 'online' };
|
const data = { id: 1, name: 'john doe', availability_status: 'online' };
|
||||||
|
@ -146,4 +167,19 @@ describe('#actions', () => {
|
||||||
expect(commit.mock.calls).toEqual([[types.SET_CONTACT_ITEM, data]]);
|
expect(commit.mock.calls).toEqual([[types.SET_CONTACT_ITEM, data]]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#deleteContactThroughConversations', () => {
|
||||||
|
it('returns correct mutations', () => {
|
||||||
|
actions.deleteContactThroughConversations({ commit }, contactList[0].id);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.DELETE_CONTACT, contactList[0].id],
|
||||||
|
[types.CLEAR_CONTACT_CONVERSATIONS, contactList[0].id, { root: true }],
|
||||||
|
[
|
||||||
|
`contactConversations/${types.DELETE_CONTACT_CONVERSATION}`,
|
||||||
|
contactList[0].id,
|
||||||
|
{ root: true },
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,10 +49,10 @@ export default [
|
||||||
name: 'Test Widget 5',
|
name: 'Test Widget 5',
|
||||||
channel_type: 'Channel::TwilioSms',
|
channel_type: 'Channel::TwilioSms',
|
||||||
avatar_url: null,
|
avatar_url: null,
|
||||||
|
medium: 'sms',
|
||||||
page_id: null,
|
page_id: null,
|
||||||
widget_color: '#68BC00',
|
widget_color: '#68BC00',
|
||||||
website_token: 'randomid125',
|
website_token: 'randomid125',
|
||||||
enable_auto_assignment: true,
|
enable_auto_assignment: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -19,6 +19,11 @@ describe('#getters', () => {
|
||||||
expect(getters.getTwilioInboxes(state).length).toEqual(1);
|
expect(getters.getTwilioInboxes(state).length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getTwilioSMSInboxes', () => {
|
||||||
|
const state = { records: inboxList };
|
||||||
|
expect(getters.getTwilioSMSInboxes(state).length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('getInbox', () => {
|
it('getInbox', () => {
|
||||||
const state = {
|
const state = {
|
||||||
records: inboxList,
|
records: inboxList,
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
||||||
CHANGE_CHAT_STATUS_FILTER: 'CHANGE_CHAT_STATUS_FILTER',
|
CHANGE_CHAT_STATUS_FILTER: 'CHANGE_CHAT_STATUS_FILTER',
|
||||||
UPDATE_ASSIGNEE: 'UPDATE_ASSIGNEE',
|
UPDATE_ASSIGNEE: 'UPDATE_ASSIGNEE',
|
||||||
UPDATE_CONVERSATION_CONTACT: 'UPDATE_CONVERSATION_CONTACT',
|
UPDATE_CONVERSATION_CONTACT: 'UPDATE_CONVERSATION_CONTACT',
|
||||||
|
CLEAR_CONTACT_CONVERSATIONS: 'CLEAR_CONTACT_CONVERSATIONS',
|
||||||
|
|
||||||
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
|
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
|
||||||
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
||||||
|
@ -101,6 +102,7 @@ export default {
|
||||||
SET_CONTACTS: 'SET_CONTACTS',
|
SET_CONTACTS: 'SET_CONTACTS',
|
||||||
CLEAR_CONTACTS: 'CLEAR_CONTACTS',
|
CLEAR_CONTACTS: 'CLEAR_CONTACTS',
|
||||||
EDIT_CONTACT: 'EDIT_CONTACT',
|
EDIT_CONTACT: 'EDIT_CONTACT',
|
||||||
|
DELETE_CONTACT: 'DELETE_CONTACT',
|
||||||
UPDATE_CONTACTS_PRESENCE: 'UPDATE_CONTACTS_PRESENCE',
|
UPDATE_CONTACTS_PRESENCE: 'UPDATE_CONTACTS_PRESENCE',
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
|
@ -119,6 +121,7 @@ export default {
|
||||||
SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
|
SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
|
||||||
SET_CONTACT_CONVERSATIONS: 'SET_CONTACT_CONVERSATIONS',
|
SET_CONTACT_CONVERSATIONS: 'SET_CONTACT_CONVERSATIONS',
|
||||||
ADD_CONTACT_CONVERSATION: 'ADD_CONTACT_CONVERSATION',
|
ADD_CONTACT_CONVERSATION: 'ADD_CONTACT_CONVERSATION',
|
||||||
|
DELETE_CONTACT_CONVERSATION: 'DELETE_CONTACT_CONVERSATION',
|
||||||
|
|
||||||
// Contact Label
|
// Contact Label
|
||||||
SET_CONTACT_LABELS_UI_FLAG: 'SET_CONTACT_LABELS_UI_FLAG',
|
SET_CONTACT_LABELS_UI_FLAG: 'SET_CONTACT_LABELS_UI_FLAG',
|
||||||
|
|
|
@ -40,8 +40,8 @@ const runSDK = ({ baseUrl, websiteToken }) => {
|
||||||
launcherTitle: chatwootSettings.launcherTitle || '',
|
launcherTitle: chatwootSettings.launcherTitle || '',
|
||||||
showPopoutButton: chatwootSettings.showPopoutButton || false,
|
showPopoutButton: chatwootSettings.showPopoutButton || false,
|
||||||
|
|
||||||
toggle() {
|
toggle(state) {
|
||||||
IFrameHelper.events.toggleBubble();
|
IFrameHelper.events.toggleBubble(state);
|
||||||
},
|
},
|
||||||
|
|
||||||
setUser(identifier, user) {
|
setUser(identifier, user) {
|
||||||
|
|
|
@ -148,8 +148,15 @@ export const IFrameHelper = {
|
||||||
setBubbleText(window.$chatwoot.launcherTitle || message.label);
|
setBubbleText(window.$chatwoot.launcherTitle || message.label);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBubble: () => {
|
toggleBubble: state => {
|
||||||
onBubbleClick();
|
let bubbleState = {};
|
||||||
|
if (state === 'open') {
|
||||||
|
bubbleState.toggleValue = true;
|
||||||
|
} else if (state === 'close') {
|
||||||
|
bubbleState.toggleValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBubbleClick(bubbleState);
|
||||||
},
|
},
|
||||||
|
|
||||||
onBubbleToggle: isOpen => {
|
onBubbleToggle: isOpen => {
|
||||||
|
|
|
@ -99,6 +99,7 @@ export const SDK_CSS = `.woot-widget-holder {
|
||||||
.woot--close::before, .woot--close::after {
|
.woot--close::before, .woot--close::after {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
|
display: inline;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
left: 32px;
|
left: 32px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -149,7 +150,7 @@ export const SDK_CSS = `.woot-widget-holder {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woot-widget-holder.has-unread-view iframe {
|
.woot-widget-holder.has-unread-view iframe {
|
||||||
min-height: unset !important;
|
min-height: unset !important;
|
||||||
}
|
}
|
||||||
|
@ -157,7 +158,7 @@ export const SDK_CSS = `.woot-widget-holder {
|
||||||
.woot-widget-holder.has-unread-view.woot-elements--left {
|
.woot-widget-holder.has-unread-view.woot-elements--left {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woot-widget-bubble.woot--close {
|
.woot-widget-bubble.woot--close {
|
||||||
bottom: 60px;
|
bottom: 60px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
@ -41,10 +41,11 @@ export default {
|
||||||
@import '~widget/assets/scss/variables.scss';
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
border: 1px solid $color-woot;
|
|
||||||
border-radius: $space-jumbo;
|
border-radius: $space-jumbo;
|
||||||
|
border: 1px solid $color-woot;
|
||||||
float: left;
|
float: left;
|
||||||
margin: $space-smaller;
|
margin: $space-smaller;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
.option-button {
|
.option-button {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
@ -52,7 +53,11 @@ export default {
|
||||||
border: 0;
|
border: 0;
|
||||||
color: $color-woot;
|
color: $color-woot;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.5;
|
||||||
|
min-height: $space-two * 2;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
:src="selectedItem.thumbnail"
|
:src="selectedItem.thumbnail"
|
||||||
size="24px"
|
size="24px"
|
||||||
:status="selectedItem.availability_status"
|
:status="selectedItem.availability_status"
|
||||||
:badge="selectedItem.channel"
|
|
||||||
:username="selectedItem.name"
|
:username="selectedItem.name"
|
||||||
/>
|
/>
|
||||||
<div class="selector-name-wrap">
|
<div class="selector-name-wrap">
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
export const isPhoneE164 = value => !!value.match(/^\+[1-9]\d{1,14}$/);
|
export const isPhoneE164 = value => !!value.match(/^\+[1-9]\d{1,14}$/);
|
||||||
export const isPhoneE164OrEmpty = value => isPhoneE164(value) || value === '';
|
export const isPhoneE164OrEmpty = value => isPhoneE164(value) || value === '';
|
||||||
|
export const shouldBeUrl = (value = '') =>
|
||||||
|
value ? value.startsWith('http') : true;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { shouldBeUrl } from '../Validators';
|
||||||
|
|
||||||
|
describe('#shouldBeUrl', () => {
|
||||||
|
it('should return correct url', () => {
|
||||||
|
expect(shouldBeUrl('http')).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +1,28 @@
|
||||||
|
import { isEscape } from '../helpers/KeyboardHelpers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keydown', this.handleKeyEvents);
|
document.addEventListener('keydown', this.onKeyDownHandler);
|
||||||
},
|
},
|
||||||
destroyed() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('keydown', this.handleKeyEvents);
|
document.removeEventListener('keydown', this.onKeyDownHandler);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onKeyDownHandler(e) {
|
||||||
|
const isEventFromAnInputBox =
|
||||||
|
e.target?.tagName === 'INPUT' || e.target?.tagName === 'TEXTAREA';
|
||||||
|
const isEventFromProseMirror = e.target?.className?.includes(
|
||||||
|
'ProseMirror'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isEventFromAnInputBox || isEventFromProseMirror) {
|
||||||
|
if (isEscape(e)) {
|
||||||
|
e.target.blur();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleKeyEvents(e);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,8 @@ export const INBOX_TYPES = {
|
||||||
TWILIO: 'Channel::TwilioSms',
|
TWILIO: 'Channel::TwilioSms',
|
||||||
API: 'Channel::Api',
|
API: 'Channel::Api',
|
||||||
EMAIL: 'Channel::Email',
|
EMAIL: 'Channel::Email',
|
||||||
|
TELEGRAM: 'Channel::Telegram',
|
||||||
|
LINE: 'Channel::Line',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -27,16 +29,41 @@ export default {
|
||||||
isATwilioChannel() {
|
isATwilioChannel() {
|
||||||
return this.channelType === INBOX_TYPES.TWILIO;
|
return this.channelType === INBOX_TYPES.TWILIO;
|
||||||
},
|
},
|
||||||
|
isALineChannel() {
|
||||||
|
return this.channelType === INBOX_TYPES.LINE;
|
||||||
|
},
|
||||||
isAnEmailChannel() {
|
isAnEmailChannel() {
|
||||||
return this.channelType === INBOX_TYPES.EMAIL;
|
return this.channelType === INBOX_TYPES.EMAIL;
|
||||||
},
|
},
|
||||||
isATwilioSMSChannel() {
|
isATwilioSMSChannel() {
|
||||||
const { phone_number: phoneNumber = '' } = this.inbox;
|
const { medium: medium = '' } = this.inbox;
|
||||||
return this.isATwilioChannel && !phoneNumber.startsWith('whatsapp');
|
return this.isATwilioChannel && medium === 'sms';
|
||||||
},
|
},
|
||||||
isATwilioWhatsappChannel() {
|
isATwilioWhatsappChannel() {
|
||||||
const { phone_number: phoneNumber = '' } = this.inbox;
|
const { medium: medium = '' } = this.inbox;
|
||||||
return this.isATwilioChannel && phoneNumber.startsWith('whatsapp');
|
return this.isATwilioChannel && medium === 'whatsapp';
|
||||||
|
},
|
||||||
|
isTwitterInboxTweet() {
|
||||||
|
return (
|
||||||
|
this.chat &&
|
||||||
|
this.chat.additional_attributes &&
|
||||||
|
this.chat.additional_attributes.type === 'tweet'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
twilioBadge() {
|
||||||
|
return `${this.isATwilioSMSChannel ? 'sms' : 'whatsapp'}`;
|
||||||
|
},
|
||||||
|
twitterBadge() {
|
||||||
|
return `${this.isTwitterInboxTweet ? 'twitter-tweet' : 'twitter-chat'}`;
|
||||||
|
},
|
||||||
|
inboxBadge() {
|
||||||
|
if (this.isATwitterInbox) {
|
||||||
|
return this.twitterBadge;
|
||||||
|
}
|
||||||
|
if (this.isATwilioChannel) {
|
||||||
|
return this.twilioBadge;
|
||||||
|
}
|
||||||
|
return this.channelType;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,7 +70,23 @@ describe('inboxMixin', () => {
|
||||||
return {
|
return {
|
||||||
inbox: {
|
inbox: {
|
||||||
channel_type: 'Channel::TwilioSms',
|
channel_type: 'Channel::TwilioSms',
|
||||||
phone_number: '+91944444444',
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isATwilioChannel).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isATwilioSMSChannel returns true if channel type is Twilio and medium is SMS', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [inboxMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inbox: {
|
||||||
|
channel_type: 'Channel::TwilioSms',
|
||||||
|
medium: 'sms',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -80,7 +96,7 @@ describe('inboxMixin', () => {
|
||||||
expect(wrapper.vm.isATwilioSMSChannel).toBe(true);
|
expect(wrapper.vm.isATwilioSMSChannel).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('isATwilioWhatsappChannel returns true if channel type is Twilio and phonenumber is a whatsapp number', () => {
|
it('isATwilioWhatsappChannel returns true if channel type is Twilio and medium is whatsapp', () => {
|
||||||
const Component = {
|
const Component = {
|
||||||
render() {},
|
render() {},
|
||||||
mixins: [inboxMixin],
|
mixins: [inboxMixin],
|
||||||
|
@ -88,7 +104,7 @@ describe('inboxMixin', () => {
|
||||||
return {
|
return {
|
||||||
inbox: {
|
inbox: {
|
||||||
channel_type: 'Channel::TwilioSms',
|
channel_type: 'Channel::TwilioSms',
|
||||||
phone_number: 'whatsapp:+91944444444',
|
medium: 'whatsapp',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -111,4 +127,79 @@ describe('inboxMixin', () => {
|
||||||
const wrapper = shallowMount(Component);
|
const wrapper = shallowMount(Component);
|
||||||
expect(wrapper.vm.isAnEmailChannel).toBe(true);
|
expect(wrapper.vm.isAnEmailChannel).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('isTwitterInboxTweet returns true if Twitter channel type is tweet', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [inboxMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chat: {
|
||||||
|
channel_type: 'Channel::TwitterProfile',
|
||||||
|
additional_attributes: {
|
||||||
|
type: 'tweet',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isTwitterInboxTweet).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('twilioBadge returns string sms if channel type is Twilio and medium is sms', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [inboxMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inbox: {
|
||||||
|
channel_type: 'Channel::TwilioSms',
|
||||||
|
medium: 'sms',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isATwilioSMSChannel).toBe(true);
|
||||||
|
expect(wrapper.vm.twilioBadge).toBe('sms');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('twitterBadge returns string twitter-tweet if Twitter channel type is tweet', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [inboxMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chat: {
|
||||||
|
id: 1,
|
||||||
|
additional_attributes: {
|
||||||
|
type: 'tweet',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isTwitterInboxTweet).toBe(true);
|
||||||
|
expect(wrapper.vm.twitterBadge).toBe('twitter-tweet');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inboxBadge returns string Channel::Telegram if isATwilioChannel and isATwitterInbox is false', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [inboxMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inbox: {
|
||||||
|
channel_type: 'Channel::Telegram',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isATwilioChannel).toBe(false);
|
||||||
|
expect(wrapper.vm.isATwitterInbox).toBe(false);
|
||||||
|
expect(wrapper.vm.channelType).toBe('Channel::Telegram');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,7 @@ class ContactIpLookupJob < ApplicationJob
|
||||||
geocoder_result = Geocoder.search(ip).first
|
geocoder_result = Geocoder.search(ip).first
|
||||||
return unless geocoder_result
|
return unless geocoder_result
|
||||||
|
|
||||||
|
contact.additional_attributes ||= {}
|
||||||
contact.additional_attributes['city'] = geocoder_result.city
|
contact.additional_attributes['city'] = geocoder_result.city
|
||||||
contact.additional_attributes['country'] = geocoder_result.country
|
contact.additional_attributes['country'] = geocoder_result.country
|
||||||
contact.additional_attributes['country_code'] = geocoder_result.country_code
|
contact.additional_attributes['country_code'] = geocoder_result.country_code
|
||||||
|
@ -34,7 +35,7 @@ class ContactIpLookupJob < ApplicationJob
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_contact_ip(contact)
|
def get_contact_ip(contact)
|
||||||
contact.additional_attributes['updated_at_ip'] || contact.additional_attributes['created_at_ip']
|
contact.additional_attributes&.dig('updated_at_ip') || contact.additional_attributes&.dig('created_at_ip')
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_look_up_db
|
def ensure_look_up_db
|
||||||
|
|
11
app/jobs/labels/update_job.rb
Normal file
11
app/jobs/labels/update_job.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
class Labels::UpdateJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
def perform(new_label_title, old_label_title, account_id)
|
||||||
|
Labels::UpdateService.new(
|
||||||
|
new_label_title: new_label_title,
|
||||||
|
old_label_title: old_label_title,
|
||||||
|
account_id: account_id
|
||||||
|
).perform
|
||||||
|
end
|
||||||
|
end
|
|
@ -111,6 +111,13 @@ class ActionCableListener < BaseListener
|
||||||
broadcast(account, tokens, CONTACT_MERGED, contact.push_event_data)
|
broadcast(account, tokens, CONTACT_MERGED, contact.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contact_deleted(event)
|
||||||
|
contact, account = extract_contact_and_account(event)
|
||||||
|
tokens = user_tokens(account, account.agents)
|
||||||
|
|
||||||
|
broadcast(account, tokens, CONTACT_DELETED, contact.push_event_data)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def typing_event_listener_tokens(account, conversation, user)
|
def typing_event_listener_tokens(account, conversation, user)
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ApplicationMailbox < ActionMailbox::Base
|
||||||
proc do |inbound_mail_obj|
|
proc do |inbound_mail_obj|
|
||||||
is_a_support_email = false
|
is_a_support_email = false
|
||||||
inbound_mail_obj.mail.to&.each do |email|
|
inbound_mail_obj.mail.to&.each do |email|
|
||||||
channel = Channel::Email.find_by('email = ? OR forward_to_email = ?', email, email)
|
channel = Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', email.downcase, email.downcase)
|
||||||
if channel.present?
|
if channel.present?
|
||||||
is_a_support_email = true
|
is_a_support_email = true
|
||||||
break
|
break
|
||||||
|
|
|
@ -21,7 +21,7 @@ class SupportMailbox < ApplicationMailbox
|
||||||
|
|
||||||
def find_channel
|
def find_channel
|
||||||
mail.to.each do |email|
|
mail.to.each do |email|
|
||||||
@channel = Channel::Email.find_by('email = ? OR forward_to_email = ?', email, email)
|
@channel = Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', email.downcase, email.downcase)
|
||||||
break if @channel.present?
|
break if @channel.present?
|
||||||
end
|
end
|
||||||
raise 'Email channel/inbox not found' if @channel.nil?
|
raise 'Email channel/inbox not found' if @channel.nil?
|
||||||
|
@ -82,6 +82,6 @@ class SupportMailbox < ApplicationMailbox
|
||||||
end
|
end
|
||||||
|
|
||||||
def identify_contact_name
|
def identify_contact_name
|
||||||
processed_mail.from.first.split('@').first
|
processed_mail.sender_name || processed_mail.from.first.split('@').first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,7 @@ class Attachment < ApplicationRecord
|
||||||
enum file_type: [:image, :audio, :video, :file, :location, :fallback]
|
enum file_type: [:image, :audio, :video, :file, :location, :fallback]
|
||||||
|
|
||||||
def push_event_data
|
def push_event_data
|
||||||
|
return unless file_type
|
||||||
return base_data.merge(location_metadata) if file_type.to_sym == :location
|
return base_data.merge(location_metadata) if file_type.to_sym == :location
|
||||||
return base_data.merge(fallback_data) if file_type.to_sym == :fallback
|
return base_data.merge(fallback_data) if file_type.to_sym == :fallback
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ class Contact < ApplicationRecord
|
||||||
before_validation :prepare_email_attribute
|
before_validation :prepare_email_attribute
|
||||||
after_create_commit :dispatch_create_event, :ip_lookup
|
after_create_commit :dispatch_create_event, :ip_lookup
|
||||||
after_update_commit :dispatch_update_event
|
after_update_commit :dispatch_update_event
|
||||||
|
after_destroy_commit :dispatch_destroy_event
|
||||||
|
|
||||||
def get_source_id(inbox_id)
|
def get_source_id(inbox_id)
|
||||||
contact_inboxes.find_by!(inbox_id: inbox_id).source_id
|
contact_inboxes.find_by!(inbox_id: inbox_id).source_id
|
||||||
|
@ -73,7 +74,8 @@ class Contact < ApplicationRecord
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
avatar: avatar_url,
|
avatar: avatar_url,
|
||||||
type: 'contact'
|
type: 'contact',
|
||||||
|
account: account.webhook_data
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -98,4 +100,8 @@ class Contact < ApplicationRecord
|
||||||
def dispatch_update_event
|
def dispatch_update_event
|
||||||
Rails.configuration.dispatcher.dispatch(CONTACT_UPDATED, Time.zone.now, contact: self)
|
Rails.configuration.dispatcher.dispatch(CONTACT_UPDATED, Time.zone.now, contact: self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dispatch_destroy_event
|
||||||
|
Rails.configuration.dispatcher.dispatch(CONTACT_DELETED, Time.zone.now, contact: self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# additional_attributes :jsonb
|
# additional_attributes :jsonb
|
||||||
# agent_last_seen_at :datetime
|
# agent_last_seen_at :datetime
|
||||||
|
# assignee_last_seen_at :datetime
|
||||||
# contact_last_seen_at :datetime
|
# contact_last_seen_at :datetime
|
||||||
|
# custom_attributes :jsonb
|
||||||
# identifier :string
|
# identifier :string
|
||||||
# last_activity_at :datetime not null
|
# last_activity_at :datetime not null
|
||||||
# snoozed_until :datetime
|
# snoozed_until :datetime
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Inbox < ApplicationRecord
|
||||||
include Avatarable
|
include Avatarable
|
||||||
include OutOfOffisable
|
include OutOfOffisable
|
||||||
|
|
||||||
|
validates :name, presence: true
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :timezone, inclusion: { in: TZInfo::Timezone.all_identifiers }
|
validates :timezone, inclusion: { in: TZInfo::Timezone.all_identifiers }
|
||||||
|
|
||||||
|
@ -82,6 +83,10 @@ class Inbox < ApplicationRecord
|
||||||
channel_type == 'Channel::Email'
|
channel_type == 'Channel::Email'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def twilio?
|
||||||
|
channel_type == 'Channel::TwilioSms'
|
||||||
|
end
|
||||||
|
|
||||||
def inbox_type
|
def inbox_type
|
||||||
channel.name
|
channel.name
|
||||||
end
|
end
|
||||||
|
@ -93,9 +98,9 @@ class Inbox < ApplicationRecord
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def webhook_url
|
def callback_webhook_url
|
||||||
case channel_type
|
case channel_type
|
||||||
when 'Channel::TwilioSMS'
|
when 'Channel::TwilioSms'
|
||||||
"#{ENV['FRONTEND_URL']}/twilio/callback"
|
"#{ENV['FRONTEND_URL']}/twilio/callback"
|
||||||
when 'Channel::Line'
|
when 'Channel::Line'
|
||||||
"#{ENV['FRONTEND_URL']}/webhooks/line/#{channel.line_channel_id}"
|
"#{ENV['FRONTEND_URL']}/webhooks/line/#{channel.line_channel_id}"
|
||||||
|
|
|
@ -25,6 +25,8 @@ class Label < ApplicationRecord
|
||||||
format: { with: UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE },
|
format: { with: UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE },
|
||||||
uniqueness: { scope: :account_id }
|
uniqueness: { scope: :account_id }
|
||||||
|
|
||||||
|
after_update_commit :update_associated_models
|
||||||
|
|
||||||
before_validation do
|
before_validation do
|
||||||
self.title = title.downcase if attribute_present?('title')
|
self.title = title.downcase if attribute_present?('title')
|
||||||
end
|
end
|
||||||
|
@ -40,4 +42,12 @@ class Label < ApplicationRecord
|
||||||
def events
|
def events
|
||||||
account.events.where(conversation_id: conversations.pluck(:id))
|
account.events.where(conversation_id: conversations.pluck(:id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def update_associated_models
|
||||||
|
return unless title_previously_changed?
|
||||||
|
|
||||||
|
Labels::UpdateJob.perform_later(title, title_previously_was, account_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,4 +40,12 @@ class Team < ApplicationRecord
|
||||||
def remove_member(user_id)
|
def remove_member(user_id)
|
||||||
team_members.find_by(user_id: user_id)&.destroy
|
team_members.find_by(user_id: user_id)&.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def messages
|
||||||
|
account.messages.where(conversation_id: conversations.pluck(:id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def events
|
||||||
|
account.events.where(conversation_id: conversations.pluck(:id))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,10 +43,10 @@ class WorkingHour < ApplicationRecord
|
||||||
def open_at?(time)
|
def open_at?(time)
|
||||||
return false if closed_all_day?
|
return false if closed_all_day?
|
||||||
|
|
||||||
time.hour >= open_hour &&
|
open_time = Time.zone.now.in_time_zone(inbox.timezone).change({ hour: open_hour, min: open_minutes })
|
||||||
time.min >= open_minutes &&
|
close_time = Time.zone.now.in_time_zone(inbox.timezone).change({ hour: close_hour, min: close_minutes })
|
||||||
time.hour <= close_hour &&
|
|
||||||
time.min <= close_minutes
|
time.between?(open_time, close_time)
|
||||||
end
|
end
|
||||||
|
|
||||||
def open_now?
|
def open_now?
|
||||||
|
|
|
@ -30,4 +30,8 @@ class ContactPolicy < ApplicationPolicy
|
||||||
def create?
|
def create?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -82,6 +82,10 @@ class MailPresenter < SimpleDelegator
|
||||||
@mail.from.map(&:downcase)
|
@mail.from.map(&:downcase)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sender_name
|
||||||
|
Mail::Address.new(@mail[:from].value).name
|
||||||
|
end
|
||||||
|
|
||||||
def original_sender
|
def original_sender
|
||||||
@mail['X-Original-Sender'].try(:value) || from.first
|
@mail['X-Original-Sender'].try(:value) || from.first
|
||||||
end
|
end
|
||||||
|
|
35
app/services/labels/update_service.rb
Normal file
35
app/services/labels/update_service.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
class Labels::UpdateService
|
||||||
|
pattr_initialize [:new_label_title!, :old_label_title!, :account_id!]
|
||||||
|
|
||||||
|
def perform
|
||||||
|
tagged_conversations.find_in_batches do |conversation_batch|
|
||||||
|
conversation_batch.each do |conversation|
|
||||||
|
conversation.label_list.remove(old_label_title)
|
||||||
|
conversation.label_list.add(new_label_title)
|
||||||
|
conversation.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tagged_contacts.find_in_batches do |contact_batch|
|
||||||
|
contact_batch.each do |contact|
|
||||||
|
contact.label_list.remove(old_label_title)
|
||||||
|
contact.label_list.add(new_label_title)
|
||||||
|
contact.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def tagged_conversations
|
||||||
|
account.conversations.tagged_with(old_label_title)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tagged_contacts
|
||||||
|
account.contacts.tagged_with(old_label_title)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account
|
||||||
|
@account ||= Account.find(account_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,17 @@
|
||||||
|
# ref : https://developers.line.biz/en/docs/messaging-api/receiving-messages/#webhook-event-types
|
||||||
|
# https://developers.line.biz/en/reference/messaging-api/#message-event
|
||||||
|
|
||||||
class Line::IncomingMessageService
|
class Line::IncomingMessageService
|
||||||
include ::FileTypeHelper
|
include ::FileTypeHelper
|
||||||
pattr_initialize [:inbox!, :params!]
|
pattr_initialize [:inbox!, :params!]
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
|
# probably test events
|
||||||
|
return if params[:events].blank?
|
||||||
|
|
||||||
line_contact_info
|
line_contact_info
|
||||||
|
return if line_contact_info['userId'].blank?
|
||||||
|
|
||||||
set_contact
|
set_contact
|
||||||
set_conversation
|
set_conversation
|
||||||
# TODO: iterate over the events and handle the attachments in future
|
# TODO: iterate over the events and handle the attachments in future
|
||||||
|
|
|
@ -14,14 +14,18 @@ class MessageTemplates::HookExecutionService
|
||||||
delegate :contact, to: :conversation
|
delegate :contact, to: :conversation
|
||||||
|
|
||||||
def trigger_templates
|
def trigger_templates
|
||||||
# TODO: let's see whether this is needed and remove this and related logic if not
|
::MessageTemplates::Template::OutOfOffice.new(conversation: conversation).perform if should_send_out_of_office_message?
|
||||||
# ::MessageTemplates::Template::OutOfOffice.new(conversation: conversation).perform if should_send_out_of_office_message?
|
|
||||||
::MessageTemplates::Template::Greeting.new(conversation: conversation).perform if should_send_greeting?
|
::MessageTemplates::Template::Greeting.new(conversation: conversation).perform if should_send_greeting?
|
||||||
::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if inbox.enable_email_collect && should_send_email_collect?
|
::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if inbox.enable_email_collect && should_send_email_collect?
|
||||||
::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform if should_send_csat_survey?
|
::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform if should_send_csat_survey?
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_send_out_of_office_message?
|
def should_send_out_of_office_message?
|
||||||
|
# should not send if its a tweet message
|
||||||
|
return false if conversation.tweet?
|
||||||
|
# should not send for outbound messages
|
||||||
|
return false unless message.incoming?
|
||||||
|
|
||||||
inbox.out_of_office? && conversation.messages.today.template.empty? && inbox.out_of_office_message.present?
|
inbox.out_of_office? && conversation.messages.today.template.empty? && inbox.out_of_office_message.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue