Merge branch 'develop' into chore/chat-list-design

This commit is contained in:
Muhsin Keloth 2022-02-08 14:39:39 +05:30 committed by GitHub
commit 6012b90c78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
726 changed files with 19046 additions and 2773 deletions

View file

@ -22,6 +22,9 @@ checks:
enabled: true
config:
threshold: 300
method-lines:
config:
threshold: 50
exclude_patterns:
- 'spec/'
- '**/specs/'
@ -44,3 +47,5 @@ exclude_patterns:
- 'app/javascript/shared/constants/countries.js'
- 'app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/languages.js'
- 'app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js'
- 'app/javascript/dashboard/routes/dashboard/settings/automation/constants.js'
- 'app/javascript/dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'

View file

@ -102,7 +102,7 @@ gem 'sentry-ruby'
gem 'sentry-sidekiq'
##-- background job processing --##
gem 'sidekiq'
gem 'sidekiq', '~> 6.4.0'
# We want cron jobs
gem 'sidekiq-cron'

View file

@ -447,7 +447,7 @@ GEM
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.4.0)
redis (4.5.1)
redis-namespace (1.8.1)
redis (>= 3.0.4)
regexp_parser (2.1.1)
@ -546,7 +546,7 @@ GEM
sexp_processor (4.15.3)
shoulda-matchers (5.0.0)
activesupport (>= 5.2.0)
sidekiq (6.2.2)
sidekiq (6.4.0)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@ -726,7 +726,7 @@ DEPENDENCIES
sentry-ruby
sentry-sidekiq
shoulda-matchers
sidekiq
sidekiq (~> 6.4.0)
sidekiq-cron
simplecov (= 0.17.1)
slack-ruby-client

View file

@ -2,7 +2,7 @@
class AccountBuilder
include CustomExceptions::Account
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password]
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin]
def perform
if @user.nil?
@ -65,6 +65,7 @@ class AccountBuilder
password: user_password,
password_confirmation: user_password,
name: @user_full_name)
@user.type = 'SuperAdmin' if @super_admin
@user.confirm if @confirmed
@user.save!
end

View file

@ -4,7 +4,7 @@ class ContactInboxBuilder
def perform
@contact = Contact.find(contact_id)
@inbox = @contact.account.inboxes.find(inbox_id)
return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
return unless ['Channel::TwilioSms', 'Channel::Sms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
source_id = @source_id || generate_source_id
create_contact_inbox(source_id) if source_id.present?
@ -13,12 +13,18 @@ class ContactInboxBuilder
private
def generate_source_id
return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms'
return wa_source_id if @inbox.channel_type == 'Channel::Whatsapp'
return @contact.email if @inbox.channel_type == 'Channel::Email'
return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api'
nil
case @inbox.channel_type
when 'Channel::TwilioSms'
twilio_source_id
when 'Channel::Whatsapp'
wa_source_id
when 'Channel::Email'
@contact.email
when 'Channel::Sms'
@contact.phone_number
when 'Channel::Api'
SecureRandom.uuid
end
end
def wa_source_id

View file

@ -31,7 +31,6 @@ class Messages::MessageBuilder
@attachments.each do |uploaded_attachment|
@message.attachments.build(
account_id: @message.account_id,
file_type: file_type(uploaded_attachment&.content_type),
file: uploaded_attachment
)
end

View file

@ -1,5 +1,6 @@
class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_automation_rule, only: [:show, :update, :destroy, :clone]
def index
@automation_rules = Current.account.automation_rules.active
@ -9,13 +10,35 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
@automation_rule = Current.account.automation_rules.create(automation_rules_permit)
end
def show; end
def update
@automation_rule.update(automation_rules_permit)
end
def destroy
@automation_rule.destroy!
head :ok
end
def clone
automation_rule = Current.account.automation_rules.find_by(id: params[:automation_rule_id])
new_rule = automation_rule.dup
new_rule.save
@automation_rule = new_rule
end
private
def automation_rules_permit
params.permit(
:name, :description, :event_name, :account_id,
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
actions: [:action_name, { action_params: [:intiated_at] }]
actions: [:action_name, { action_params: [] }]
)
end
def fetch_automation_rule
@automation_rule = Current.account.automation_rules.find_by(id: params[:id])
end
end

View file

@ -18,7 +18,7 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
def show; end
def update
@campaign.update(campaign_params)
@campaign.update!(campaign_params)
end
private

View file

@ -91,20 +91,9 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end
def create_channel
case permitted_params[:channel][:type]
when 'web_widget'
Current.account.web_widgets.create!(permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].except(:type))
when 'api'
Current.account.api_channels.create!(permitted_params(Channel::Api::EDITABLE_ATTRS)[:channel].except(:type))
when 'email'
Current.account.email_channels.create!(permitted_params(Channel::Email::EDITABLE_ATTRS)[:channel].except(:type))
when 'line'
Current.account.line_channels.create!(permitted_params(Channel::Line::EDITABLE_ATTRS)[:channel].except(:type))
when 'telegram'
Current.account.telegram_channels.create!(permitted_params(Channel::Telegram::EDITABLE_ATTRS)[:channel].except(:type))
when 'whatsapp'
Current.account.whatsapp_channels.create!(permitted_params(Channel::Whatsapp::EDITABLE_ATTRS)[:channel].except(:type))
end
return unless %w[web_widget api email line telegram whatsapp sms].include?(permitted_params[:channel][:type])
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
end
def update_channel_feature_flags
@ -123,6 +112,30 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
)
end
def channel_type_from_params
{
'web_widget' => Channel::WebWidget,
'api' => Channel::Api,
'email' => Channel::Email,
'line' => Channel::Line,
'telegram' => Channel::Telegram,
'whatsapp' => Channel::Whatsapp,
'sms' => Channel::Sms
}[permitted_params[:channel][:type]]
end
def account_channels_method
{
'web_widget' => Current.account.web_widgets,
'api' => Current.account.api_channels,
'email' => Current.account.email_channels,
'line' => Current.account.line_channels,
'telegram' => Current.account.telegram_channels,
'whatsapp' => Current.account.whatsapp_channels,
'sms' => Current.account.sms_channels
}[permitted_params[:channel][:type]]
end
def get_channel_attributes(channel_type)
if channel_type.constantize.const_defined?('EDITABLE_ATTRS')
channel_type.constantize::EDITABLE_ATTRS.presence

View file

@ -38,6 +38,8 @@ class Api::V1::ProfilesController < Api::BaseController
:name,
:display_name,
:avatar,
:message_signature,
:message_signature_enabled,
ui_settings: {}
)
end

View file

@ -31,7 +31,6 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
params[:message][:attachments].each do |uploaded_attachment|
@message.attachments.new(
account_id: @message.account_id,
file_type: helpers.file_type(uploaded_attachment&.content_type),
file: uploaded_attachment
)
end

View file

@ -10,6 +10,7 @@ class Installation::OnboardingController < ApplicationController
user_full_name: onboarding_params.dig(:user, :name),
email: onboarding_params.dig(:user, :email),
user_password: params.dig(:user, :password),
super_admin: true,
confirmed: true
).perform
rescue StandardError => e

View file

@ -13,7 +13,8 @@ class Platform::Api::V1::UsersController < PlatformController
end
def login
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{@resource.email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
encoded_email = ERB::Util.url_encode(@resource.email)
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{encoded_email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
end
def show; end

View file

@ -8,7 +8,7 @@ class SuperAdmin::Devise::SessionsController < Devise::SessionsController
def create
redirect_to(super_admin_session_path, flash: { error: @error_message }) && return unless valid_credentials?
sign_in(@super_admin, scope: :super_admin)
sign_in(:super_admin, @super_admin)
flash.discard
redirect_to super_admin_users_path
end

View file

@ -1,44 +0,0 @@
class SuperAdmin::SuperAdminsController < SuperAdmin::ApplicationController
# Overwrite any of the RESTful controller actions to implement custom behavior
# For example, you may want to send an email after a foo is updated.
#
# def update
# super
# send_foo_updated_email(requested_resource)
# end
# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, and `update`
# actions.
#
# def find_resource(param)
# Foo.find_by!(slug: param)
# end
# The result of this lookup will be available as `requested_resource`
# Override this if you have certain roles that require a subset
# this will be used to set the records shown on the `index` action.
#
# def scoped_resource
# if current_user.super_admin?
# resource_class
# else
# resource_class.with_less_stuff
# end
# end
# Override `resource_params` if you want to transform the submitted
# data before it's persisted. For example, the following would turn all
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes).
# transform_values { |value| value == "" ? nil : value }
# end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
end

View file

@ -33,12 +33,15 @@ class SuperAdmin::UsersController < SuperAdmin::ApplicationController
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes).
# transform_values { |value| value == "" ? nil : value }
# end
def resource_params
permitted_params = super
permitted_params.delete(:password) if permitted_params[:password].blank?
permitted_params
end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
def find_resource(param)
super.becomes(User)
end
end

View file

@ -0,0 +1,6 @@
class Webhooks::SmsController < ActionController::API
def process_payload
Webhooks::SmsEventsJob.perform_later(params['_json']&.first&.to_unsafe_hash)
head :ok
end
end

View file

@ -1,75 +0,0 @@
require 'administrate/base_dashboard'
class SuperAdminDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
email: Field::String,
password: Field::Password,
remember_created_at: Field::DateTime,
sign_in_count: Field::Number,
current_sign_in_at: Field::DateTime,
last_sign_in_at: Field::DateTime,
current_sign_in_ip: Field::String.with_options(searchable: false),
last_sign_in_ip: Field::String.with_options(searchable: false),
created_at: Field::DateTime,
updated_at: Field::DateTime
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = %i[
id
email
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
id
email
remember_created_at
sign_in_count
current_sign_in_at
last_sign_in_at
current_sign_in_ip
last_sign_in_ip
created_at
updated_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
email
password
].freeze
# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
# field of the dashboard.
#
# For example to add an option to search for open resources by typing "open:"
# in the search field:
#
# COLLECTION_FILTERS = {
# open: ->(resources) { resources.where(open: true) }
# }.freeze
COLLECTION_FILTERS = {}.freeze
# Overwrite this method to customize how super admins are displayed
# across all pages of the admin dashboard.
#
# def display_resource(super_admin)
# "SuperAdmin ##{super_admin.id}"
# end
end

View file

@ -30,6 +30,7 @@ class UserDashboard < Administrate::BaseDashboard
created_at: Field::DateTime,
updated_at: Field::DateTime,
pubsub_token: Field::String,
type: Field::Select.with_options(collection: [nil, 'SuperAdmin']),
accounts: CountField
}.freeze
@ -44,6 +45,7 @@ class UserDashboard < Administrate::BaseDashboard
name
email
accounts
type
].freeze
# SHOW_PAGE_ATTRIBUTES
@ -53,10 +55,12 @@ class UserDashboard < Administrate::BaseDashboard
avatar_url
unconfirmed_email
name
type
display_name
email
created_at
updated_at
confirmed_at
account_users
].freeze
@ -68,6 +72,8 @@ class UserDashboard < Administrate::BaseDashboard
display_name
email
password
confirmed_at
type
].freeze
# COLLECTION_FILTERS

View file

@ -55,7 +55,7 @@ class ConversationFinder
def set_inboxes
@inbox_ids = if params[:inbox_id]
current_account.inboxes.where(id: params[:inbox_id])
@current_user.assigned_inboxes.where(id: params[:inbox_id])
else
@current_user.assigned_inboxes.pluck(:id)
end

View file

@ -0,0 +1,14 @@
/* global axios */
import ApiClient from './ApiClient';
class AutomationsAPI extends ApiClient {
constructor() {
super('automation_rules', { accountScoped: true });
}
clone(automationId) {
return axios.post(`${this.url}/${automationId}/clone`);
}
}
export default new AutomationsAPI();

View file

@ -6,8 +6,12 @@ class CustomViewsAPI extends ApiClient {
super('custom_filters', { accountScoped: true });
}
getCustomViews() {
return axios.get(this.url);
getCustomViewsByFilterType(type) {
return axios.get(`${this.url}?filter_type=${type}`);
}
deleteCustomViews(id, type) {
return axios.delete(`${this.url}/${id}?filter_type=${type}`);
}
}

View file

@ -7,17 +7,19 @@ export const buildCreatePayload = ({
isPrivate,
contentAttributes,
echoId,
file,
files,
ccEmails = '',
bccEmails = '',
}) => {
let payload;
if (file) {
if (files && files.length !== 0) {
payload = new FormData();
payload.append('attachments[]', file, file.name);
if (message) {
payload.append('content', message);
}
files.forEach(file => {
payload.append('attachments[]', file);
});
payload.append('private', isPrivate);
payload.append('echo_id', echoId);
payload.append('cc_emails', ccEmails);
@ -46,7 +48,7 @@ class MessageApi extends ApiClient {
private: isPrivate,
contentAttributes,
echo_id: echoId,
file,
files,
ccEmails = '',
bccEmails = '',
}) {
@ -58,7 +60,7 @@ class MessageApi extends ApiClient {
isPrivate,
contentAttributes,
echoId,
file,
files,
ccEmails,
bccEmails,
}),

View file

@ -0,0 +1,15 @@
import automations from '../automation';
import ApiClient from '../ApiClient';
describe('#AutomationsAPI', () => {
it('creates correct instance', () => {
expect(automations).toBeInstanceOf(ApiClient);
expect(automations).toHaveProperty('get');
expect(automations).toHaveProperty('show');
expect(automations).toHaveProperty('create');
expect(automations).toHaveProperty('update');
expect(automations).toHaveProperty('delete');
expect(automations).toHaveProperty('clone');
expect(automations.url).toBe('/api/v1/automation_rules');
});
});

View file

@ -36,7 +36,7 @@ describe('#ConversationAPI', () => {
echoId: 12,
isPrivate: true,
file: new Blob(['test-content'], { type: 'application/pdf' }),
files: [new Blob(['test-content'], { type: 'application/pdf' })],
});
expect(formPayload).toBeInstanceOf(FormData);
expect(formPayload.get('content')).toEqual('test content');

View file

@ -223,8 +223,14 @@
@include flex-align(right, null);
.wrap {
align-items: flex-end;
display: flex;
margin-right: $space-normal;
text-align: right;
.sender--info {
padding: var(--space-small) 0 var(--space-smaller) var(--space-small);
}
}
.bubble {

View file

@ -3,7 +3,7 @@
<slot></slot>
<div
class="chat-list__top"
:class="{ filter__applied: hasAppliedFiltersOrActiveCustomViews }"
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
>
<h1 class="page-sub-title text-truncate" :title="pageTitle">
{{ pageTitle }}
@ -11,17 +11,17 @@
<div class="filter--actions">
<chat-filter
v-if="!hasAppliedFiltersOrActiveCustomViews"
v-if="!hasAppliedFiltersOrActiveFolders"
@statusFilterChange="updateStatusType"
/>
<div v-if="hasAppliedFilters && !hasActiveCustomViews">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="save"
@click="onClickOpenAddCustomViewsModal"
@click="onClickOpenAddFoldersModal"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
@ -32,7 +32,7 @@
@click="resetAndFetchData"
/>
</div>
<div v-if="hasActiveCustomViews">
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
@ -40,7 +40,7 @@
color-scheme="alert"
icon="delete"
class="delete-custom-view__button"
@click="onClickOpenDeleteCustomViewsModal"
@click="onClickOpenDeleteFoldersModal"
/>
</div>
@ -59,21 +59,23 @@
</div>
<add-custom-views
v-if="showAddCustomViewsModal"
:custom-views-query="customViewsQuery"
@close="onCloseAddCustomViewsModal"
v-if="showAddFoldersModal"
:custom-views-query="foldersQuery"
:open-last-saved-item="openLastSavedItemInFolder"
@close="onCloseAddFoldersModal"
/>
<delete-custom-views
v-if="showDeleteCustomViewsModal"
:show-delete-popup.sync="showDeleteCustomViewsModal"
:active-custom-view="activeCustomView"
:custom-views-id="customViewsId"
@close="onCloseDeleteCustomViewsModal"
v-if="showDeleteFoldersModal"
:show-delete-popup.sync="showDeleteFoldersModal"
:active-custom-view="activeFolder"
:custom-views-id="foldersId"
:open-last-item-after-delete="openLastItemAfterDeleteInFolder"
@close="onCloseDeleteFoldersModal"
/>
<chat-type-tabs
v-if="!hasAppliedFiltersOrActiveCustomViews"
v-if="!hasAppliedFiltersOrActiveFolders"
:items="assigneeTabItems"
:active-tab="activeAssigneeTab"
@chatTabChange="updateAssigneeTab"
@ -89,7 +91,7 @@
:key="chat.id"
:active-label="label"
:team-id="teamId"
:custom-views-id="customViewsId"
:folders-id="foldersId"
:chat="chat"
:conversation-type="conversationType"
:show-assignee="showAssigneeInConversationCard"
@ -126,7 +128,7 @@
>
<conversation-advanced-filter
v-if="showAdvancedFilters"
:filter-types="advancedFilterTypes"
:initial-filter-types="advancedFilterTypes"
:on-close="onToggleAdvanceFiltersModal"
@applyFilter="onApplyFilter"
/>
@ -182,7 +184,7 @@ export default {
type: String,
default: '',
},
customViewsId: {
foldersId: {
type: [String, Number],
default: 0,
},
@ -196,9 +198,9 @@ export default {
...filter,
attributeName: this.$t(`FILTER.ATTRIBUTES.${filter.attributeI18nKey}`),
})),
customViewsQuery: {},
showAddCustomViewsModal: false,
showDeleteCustomViewsModal: false,
foldersQuery: {},
showAddFoldersModal: false,
showDeleteFoldersModal: false,
};
},
computed: {
@ -213,20 +215,20 @@ export default {
activeInbox: 'getSelectedInbox',
conversationStats: 'conversationStats/getStats',
appliedFilters: 'getAppliedConversationFilters',
customViews: 'customViews/getCustomViews',
folders: 'customViews/getCustomViews',
}),
hasAppliedFilters() {
return this.appliedFilters.length;
},
hasActiveCustomViews() {
return this.activeCustomView && this.customViewsId !== 0;
hasActiveFolders() {
return this.activeFolder && this.foldersId !== 0;
},
hasAppliedFiltersOrActiveCustomViews() {
return this.hasAppliedFilters || this.hasActiveCustomViews;
hasAppliedFiltersOrActiveFolders() {
return this.hasAppliedFilters || this.hasActiveFolders;
},
savedCustomViewsValue() {
if (this.hasActiveCustomViews) {
const payload = this.activeCustomView.query;
savedFoldersValue() {
if (this.hasActiveFolders) {
const payload = this.activeFolder.query;
this.fetchSavedFilteredConversations(payload);
}
return {};
@ -242,7 +244,10 @@ export default {
});
},
showAssigneeInConversationCard() {
return this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ALL;
return (
this.hasAppliedFiltersOrActiveFolders ||
this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ALL
);
},
inbox() {
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
@ -253,7 +258,7 @@ export default {
);
},
currentPageFilterKey() {
return this.hasAppliedFiltersOrActiveCustomViews
return this.hasAppliedFiltersOrActiveFolders
? 'appliedFilters'
: this.activeAssigneeTab;
},
@ -278,9 +283,7 @@ export default {
conversationType: this.conversationType
? this.conversationType
: undefined,
customViews: this.hasActiveCustomViews
? this.savedCustomViewsValue
: undefined,
folders: this.hasActiveFolders ? this.savedFoldersValue : undefined,
};
},
pageTitle() {
@ -296,14 +299,14 @@ export default {
if (this.conversationType === 'mention') {
return this.$t('CHAT_LIST.MENTION_HEADING');
}
if (this.hasActiveCustomViews) {
return this.activeCustomView.name;
if (this.hasActiveFolders) {
return this.activeFolder.name;
}
return this.$t('CHAT_LIST.TAB_HEADING');
},
conversationList() {
let conversationList = [];
if (!this.hasAppliedFiltersOrActiveCustomViews) {
if (!this.hasAppliedFiltersOrActiveFolders) {
const filters = this.conversationFilters;
if (this.activeAssigneeTab === 'me') {
conversationList = [...this.mineChatsList(filters)];
@ -318,10 +321,10 @@ export default {
return conversationList;
},
activeCustomView() {
if (this.customViewsId) {
const activeView = this.customViews.filter(
view => view.id === Number(this.customViewsId)
activeFolder() {
if (this.foldersId) {
const activeView = this.folders.filter(
view => view.id === Number(this.foldersId)
);
const [firstValue] = activeView;
return firstValue;
@ -348,8 +351,10 @@ export default {
conversationType() {
this.resetAndFetchData();
},
activeCustomView() {
this.resetAndFetchData();
activeFolder() {
if (!this.hasAppliedFilters) {
this.resetAndFetchData();
}
},
},
mounted() {
@ -365,22 +370,22 @@ export default {
if (this.$route.name !== 'home') {
this.$router.push({ name: 'home' });
}
this.customViewsQuery = { payload: payload };
this.foldersQuery = filterQueryGenerator(payload);
this.$store.dispatch('conversationPage/reset');
this.$store.dispatch('emptyAllConversations');
this.fetchFilteredConversations(payload);
},
onClickOpenAddCustomViewsModal() {
this.showAddCustomViewsModal = true;
onClickOpenAddFoldersModal() {
this.showAddFoldersModal = true;
},
onCloseAddCustomViewsModal() {
this.showAddCustomViewsModal = false;
onCloseAddFoldersModal() {
this.showAddFoldersModal = false;
},
onClickOpenDeleteCustomViewsModal() {
this.showDeleteCustomViewsModal = true;
onClickOpenDeleteFoldersModal() {
this.showDeleteFoldersModal = true;
},
onCloseDeleteCustomViewsModal() {
this.showDeleteCustomViewsModal = false;
onCloseDeleteFoldersModal() {
this.showDeleteFoldersModal = false;
},
onToggleAdvanceFiltersModal() {
this.showAdvancedFilters = !this.showAdvancedFilters;
@ -433,11 +438,11 @@ export default {
this.$store.dispatch('conversationPage/reset');
this.$store.dispatch('emptyAllConversations');
this.$store.dispatch('clearConversationFilters');
if (this.hasActiveCustomViews) {
const payload = this.activeCustomView.query;
if (this.hasActiveFolders) {
const payload = this.activeFolder.query;
this.fetchSavedFilteredConversations(payload);
}
if (this.customViewsId) {
if (this.foldersId) {
return;
}
this.fetchConversations();
@ -448,11 +453,11 @@ export default {
.then(() => this.$emit('conversation-load'));
},
loadMoreConversations() {
if (!this.hasAppliedFiltersOrActiveCustomViews) {
if (!this.hasAppliedFiltersOrActiveFolders) {
this.fetchConversations();
}
if (this.hasActiveCustomViews) {
const payload = this.activeCustomView.query;
if (this.hasActiveFolders) {
const payload = this.activeFolder.query;
this.fetchSavedFilteredConversations(payload);
} else {
this.fetchFilteredConversations(this.appliedFilters);
@ -492,6 +497,22 @@ export default {
this.resetAndFetchData();
}
},
openLastSavedItemInFolder() {
const lastItemOfFolder = this.folders[this.folders.length - 1];
const lastItemId = lastItemOfFolder.id;
this.$router.push({
name: 'folder_conversations',
params: { id: lastItemId },
});
},
openLastItemAfterDeleteInFolder() {
if (this.folders.length > 0) {
this.openLastSavedItemInFolder();
} else {
this.$router.push({ name: 'home' });
this.fetchConversations();
}
},
},
};
</script>

View file

@ -63,7 +63,7 @@
rel="noopener noreferrer"
class="value"
>
{{ value || '---' }}
{{ urlValue }}
</a>
<p v-else class="value">
{{ displayValue || '---' }}
@ -119,7 +119,7 @@ import format from 'date-fns/format';
import { required, url } from 'vuelidate/lib/validators';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
import { isValidURL } from '../helper/URLHelper';
const DATE_FORMAT = 'yyyy-MM-dd';
export default {
@ -184,6 +184,9 @@ export default {
isAttributeTypeDate() {
return this.attributeType === 'date';
},
urlValue() {
return isValidURL(this.value) ? this.value : '---';
},
notAttributeTypeCheckboxAndList() {
return !this.isAttributeTypeCheckbox && !this.isAttributeTypeList;
},

View file

@ -4,7 +4,12 @@
<div class="ui-notification">
<fluent-icon icon="wifi-off" />
<p class="ui-notification-text">
{{ $t('NETWORK.NOTIFICATION.TEXT') }}
{{
useInstallationName(
$t('NETWORK.NOTIFICATION.TEXT'),
globalConfig.installationName
)
}}
</p>
<woot-button variant="clear" size="small" @click="refreshPage">
{{ $t('NETWORK.BUTTON.REFRESH') }}
@ -23,13 +28,22 @@
</template>
<script>
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import { mapGetters } from 'vuex';
export default {
mixins: [globalConfigMixin],
data() {
return {
showNotification: !navigator.onLine,
};
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
},
mounted() {
window.addEventListener('offline', this.updateOnlineStatus);
},

View file

@ -88,12 +88,31 @@ export default {
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
inboxes: 'inboxes/getInboxes',
customViews: 'customViews/getCustomViews',
accountId: 'getCurrentAccountId',
currentRole: 'getCurrentRole',
labels: 'labels/getLabelsOnSidebar',
teams: 'teams/getMyTeams',
}),
activeCustomView() {
if (this.activePrimaryMenu.key === 'contacts') {
return 'contact';
}
if (this.activePrimaryMenu.key === 'conversations') {
return 'conversation';
}
return '';
},
customViews() {
return this.$store.getters['customViews/getCustomViewsByFilterType'](
this.activeCustomView
);
},
isConversationOrContactActive() {
return (
this.activePrimaryMenu.key === 'contacts' ||
this.activePrimaryMenu.key === 'conversations'
);
},
sideMenuConfig() {
return getSidebarItems(this.accountId);
},
@ -121,16 +140,27 @@ export default {
return activePrimaryMenu;
},
},
watch: {
activeCustomView() {
this.fetchCustomViews();
},
},
mounted() {
this.$store.dispatch('labels/get');
this.$store.dispatch('inboxes/get');
this.$store.dispatch('customViews/get');
this.$store.dispatch('notifications/unReadCount');
this.$store.dispatch('teams/get');
this.$store.dispatch('attributes/get');
this.fetchCustomViews();
},
methods: {
fetchCustomViews() {
if (this.isConversationOrContactActive) {
this.$store.dispatch('customViews/get', this.activeCustomView);
}
},
toggleKeyShortcutModal() {
this.showShortcutModal = true;
},

View file

@ -5,6 +5,7 @@ const contacts = accountId => ({
routes: [
'contacts_dashboard',
'contact_profile_dashboard',
'contacts_segments_dashboard',
'contacts_labels_dashboard',
],
menuItems: [

View file

@ -14,8 +14,8 @@ const conversations = accountId => ({
'conversations_through_team',
'conversation_mentions',
'conversation_through_mentions',
'custom_view_conversations',
'conversations_through_custom_view',
'folder_conversations',
'conversations_through_folders',
],
menuItems: [
{

View file

@ -69,13 +69,13 @@ const settings = accountId => ({
),
toStateName: 'attributes_list',
},
{
icon: 'autocorrect',
label: 'AUTOMATION',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
toStateName: 'automation_list',
},
// {
// icon: 'automation',
// label: 'AUTOMATION',
// hasSubMenu: false,
// toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
// toStateName: 'automation_list',
// },
{
icon: 'chat-multiple',
label: 'CANNED_RESPONSES',

View file

@ -57,6 +57,9 @@ export default {
hasSecondaryMenu() {
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
},
contactCustomViews() {
return this.customViews.filter(view => view.filter_type === 'contact');
},
accessibleMenuItems() {
if (!this.currentRole) {
return [];
@ -154,10 +157,10 @@ export default {
})),
};
},
customViewsSection() {
foldersSection() {
return {
icon: 'folder',
label: 'CUSTOM_VIEWS',
label: 'CUSTOM_VIEWS_FOLDER',
hasSubMenu: true,
key: 'custom_view',
children: this.customViews
@ -172,20 +175,39 @@ export default {
})),
};
},
contactSegmentsSection() {
return {
icon: 'folder',
label: 'CUSTOM_VIEWS_SEGMENTS',
hasSubMenu: true,
key: 'custom_view',
children: this.customViews
.filter(view => view.filter_type === 'contact')
.map(view => ({
id: view.id,
label: view.name,
truncateLabel: true,
toState: frontendURL(
`accounts/${this.accountId}/contacts/custom_view/${view.id}`
),
})),
};
},
additionalSecondaryMenuItems() {
let conversationMenuItems = [this.inboxSection, this.labelSection];
let contactMenuItems = [this.contactLabelSection];
if (this.teams.length) {
conversationMenuItems = [this.teamSection, ...conversationMenuItems];
}
if (this.customViews.length) {
conversationMenuItems = [
this.customViewsSection,
...conversationMenuItems,
];
conversationMenuItems = [this.foldersSection, ...conversationMenuItems];
}
if (this.contactCustomViews.length) {
contactMenuItems = [this.contactSegmentsSection, ...contactMenuItems];
}
return {
conversations: conversationMenuItems,
contacts: [this.contactLabelSection],
contacts: contactMenuItems,
};
},
},

View file

@ -73,14 +73,48 @@ export default {
hasSubMenu() {
return !!this.menuItem.children;
},
isInboxConversation() {
return (
this.$store.state.route.name === 'inbox_conversation' &&
this.menuItem.toStateName === 'home'
);
},
isTeamsSettings() {
return (
this.$store.state.route.name === 'settings_teams_edit' &&
this.menuItem.toStateName === 'settings_teams_list'
);
},
isInboxsSettings() {
return (
this.$store.state.route.name === 'settings_inbox_show' &&
this.menuItem.toStateName === 'settings_inbox_list'
);
},
isIntegrationsSettings() {
return (
this.$store.state.route.name === 'settings_integrations_webhook' &&
this.menuItem.toStateName === 'settings_integrations'
);
},
isApplicationsSettings() {
return (
this.$store.state.route.name === 'settings_applications_integration' &&
this.menuItem.toStateName === 'settings_applications'
);
},
computedClass() {
// If active Inbox is present
// donot highlight conversations
if (this.activeInbox) return ' ';
if (
this.$store.state.route.name === 'inbox_conversation' &&
this.menuItem.toStateName === 'home'
this.isInboxConversation ||
this.isTeamsSettings ||
this.isInboxsSettings ||
this.isIntegrationsSettings ||
this.isApplicationsSettings
) {
return 'is-active';
}

View file

@ -0,0 +1,129 @@
<template>
<div class="banner" :class="bannerClasses">
<span>
{{ bannerMessage }}
<a
v-if="hrefLink"
:href="hrefLink"
rel="noopener noreferrer nofollow"
target="_blank"
>
{{ hrefLinkText }}
</a>
</span>
<woot-button
v-if="hasActionButton"
size="small"
variant="link"
icon="arrow-right"
color-scheme="primary"
class-names="banner-action__button"
@click="onClick"
>
{{ actionButtonLabel }}
</woot-button>
<woot-button
v-if="hasCloseButton"
size="small"
variant="link"
color-scheme="warning"
icon="dismiss-circle"
class-names="banner-action__button"
@click="onClickClose"
>
</woot-button>
</div>
</template>
<script>
export default {
props: {
bannerMessage: {
type: String,
default: '',
},
hrefLink: {
type: String,
default: '',
},
hrefLinkText: {
type: String,
default: '',
},
hasActionButton: {
type: Boolean,
default: false,
},
actionButtonLabel: {
type: String,
default: '',
},
colorScheme: {
type: String,
default: '',
},
hasCloseButton: {
type: Boolean,
default: false,
},
},
computed: {
bannerClasses() {
return [this.colorScheme];
},
},
methods: {
onClick(e) {
this.$emit('click', e);
},
onClickClose(e) {
this.$emit('close', e);
},
},
};
</script>
<style lang="scss" scoped>
.banner {
display: flex;
color: var(--white);
font-size: var(--font-size-mini);
padding: var(--space-slab) var(--space-normal);
justify-content: center;
position: sticky;
&.secondary {
background: var(--s-300);
}
&.alert {
background: var(--r-400);
}
&.warning {
background: var(--y-800);
color: var(--s-600);
a {
color: var(--s-600);
}
}
&.gray {
background: var(--b-500);
}
a {
text-decoration: underline;
color: var(--white);
font-size: var(--font-size-mini);
}
.banner-action__button {
margin: 0 var(--space-smaller);
::v-deep .button__content {
white-space: nowrap;
}
}
}
</style>

View file

@ -30,7 +30,7 @@ export default {
},
value: {
type: Date,
default: () => [],
default: [],
},
},

View file

@ -7,7 +7,7 @@
>
<div class="thumb-wrap">
<img
v-if="isTypeImage(attachment.resource.type)"
v-if="isTypeImage(attachment.resource.content_type)"
class="image-thumb"
:src="attachment.thumb"
/>
@ -15,12 +15,12 @@
</div>
<div class="file-name-wrap">
<span class="item">
{{ attachment.resource.name }}
{{ attachment.resource.filename }}
</span>
</div>
<div class="file-size-wrap">
<span class="item">
{{ formatFileSize(attachment.resource.size) }}
{{ formatFileSize(attachment.resource.byte_size) }}
</span>
</div>
<div class="remove-file-wrap">

View file

@ -0,0 +1,182 @@
<template>
<div
class="filter"
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
>
<div class="filter-inputs">
<select
v-model="action_name"
class="action__question"
@change="resetFilter()"
>
<option
v-for="attribute in actionTypes"
:key="attribute.key"
:value="attribute.key"
>
{{ attribute.label }}
</option>
</select>
<div class="filter__answer--wrap">
<div class="multiselect-wrap--small">
<multiselect
v-model="action_params"
track-by="id"
label="name"
:placeholder="'Select'"
:multiple="true"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
deselect-label=""
:max-height="160"
:options="dropdownValues"
:allow-empty="false"
/>
</div>
</div>
<woot-button
icon="dismiss"
variant="clear"
color-scheme="secondary"
@click="removeAction"
/>
</div>
<p
v-if="v.action_params.$dirty && v.action_params.$error"
class="filter-error"
>
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
</p>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => null,
},
actionTypes: {
type: Array,
default: () => [],
},
dropdownValues: {
type: Array,
default: () => [],
},
v: {
type: Object,
default: () => null,
},
},
computed: {
action_name: {
get() {
if (!this.value) return null;
return this.value.action_name;
},
set(value) {
const payload = this.value || {};
this.$emit('input', { ...payload, action_name: value });
},
},
action_params: {
get() {
if (!this.value) return null;
return this.value.action_params;
},
set(value) {
const payload = this.value || {};
this.$emit('input', { ...payload, action_params: value });
},
},
},
methods: {
removeAction() {
this.$emit('removeAction');
},
resetFilter() {
this.$emit('resetFilter');
},
},
};
</script>
<style lang="scss" scoped>
.filter {
background: var(--color-background);
padding: var(--space-small);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-medium);
margin-bottom: var(--space-small);
}
.filter.error {
background: var(--r-50);
}
.filter-inputs {
display: flex;
}
.filter-error {
color: var(--r-500);
display: block;
margin: var(--space-smaller) 0;
}
.action__question,
.filter__operator {
margin-bottom: var(--space-zero);
margin-right: var(--space-smaller);
}
.action__question {
max-width: 50%;
}
.filter__answer--wrap {
margin-right: var(--space-smaller);
flex-grow: 1;
input {
margin-bottom: 0;
}
}
.filter__answer {
&.answer--text-input {
margin-bottom: var(--space-zero);
}
}
.filter__join-operator-wrap {
position: relative;
z-index: var(--z-index-twenty);
margin: var(--space-zero);
}
.filter__join-operator {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin: var(--space-one) var(--space-zero);
.operator__line {
position: absolute;
width: 100%;
border-bottom: 1px solid var(--color-border);
}
.operator__select {
position: relative;
width: auto;
margin-bottom: var(--space-zero) !important;
}
}
.multiselect {
margin-bottom: var(--space-zero);
}
</style>

View file

@ -0,0 +1,75 @@
export const OPERATOR_TYPES_1 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
];
export const OPERATOR_TYPES_2 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
];
export const OPERATOR_TYPES_3 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'contains',
label: 'Contains',
},
{
value: 'does_not_contain',
label: 'Does not contain',
},
];
export const OPERATOR_TYPES_4 = [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
{
value: 'is_greater_than',
label: 'Is greater than',
},
{
value: 'is_lesser_than',
label: 'Is lesser than',
},
];

View file

@ -1,8 +1,29 @@
<template>
<div class="filters">
<div class="filter">
<div class="filter" :class="{ error: v.values.$dirty && v.values.$error }">
<div class="filter-inputs">
<select
v-if="groupedFilters"
v-model="attributeKey"
class="filter__question"
@change="resetFilter()"
>
<optgroup
v-for="(group, i) in filterGroups"
:key="i"
:label="group.name"
>
<option
v-for="attribute in group.attributes"
:key="attribute.key"
:value="attribute.key"
>
{{ attribute.name }}
</option>
</optgroup>
</select>
<select
v-else
v-model="attributeKey"
class="filter__question"
@change="resetFilter()"
@ -63,6 +84,14 @@
:option-height="104"
/>
</div>
<div v-else-if="inputType === 'date'" class="multiselect-wrap--small">
<input
v-model="values"
type="date"
:editable="false"
class="answer--text-input datepicker"
/>
</div>
<input
v-else
v-model="values"
@ -132,6 +161,14 @@ export default {
type: Boolean,
default: true,
},
groupedFilters: {
type: Boolean,
default: false,
},
filterGroups: {
type: Array,
default: () => [],
},
},
computed: {
attributeKey: {
@ -193,6 +230,10 @@ export default {
border-radius: var(--border-radius-medium);
}
.filter.error {
background: var(--r-50);
}
.filter-inputs {
display: flex;
}

View file

@ -16,9 +16,14 @@
ref="upload"
:size="4096 * 4096"
:accept="allowedFileTypes"
:multiple="enableMultipleFileUpload"
:drop="true"
:drop-directory="false"
@input-file="onFileUpload"
:data="{
direct_upload_url: '/rails/active_storage/direct_uploads',
direct_upload: true,
}"
@input-file="onDirectFileUpload"
>
<woot-button
v-if="showAttachButton"
@ -79,6 +84,7 @@
<script>
import FileUpload from 'vue-upload-component';
import * as ActiveStorage from 'activestorage';
import {
hasPressedAltAndWKey,
hasPressedAltAndAKey,
@ -108,7 +114,7 @@ export default {
type: Boolean,
default: false,
},
onFileUpload: {
onDirectFileUpload: {
type: Function,
default: () => {},
},
@ -144,6 +150,10 @@ export default {
type: Boolean,
default: true,
},
enableMultipleFileUpload: {
type: Boolean,
default: true,
},
},
computed: {
isNote() {
@ -166,6 +176,9 @@ export default {
return ALLOWED_FILE_TYPES;
},
},
mounted() {
ActiveStorage.start();
},
methods: {
handleKeyEvents(e) {
if (hasPressedAltAndWKey(e)) {

View file

@ -9,12 +9,13 @@
v-for="(filter, i) in appliedFilters"
:key="i"
v-model="appliedFilters[i]"
:filter-attributes="filterAttributes"
:filter-groups="filterGroups"
:input-type="getInputType(appliedFilters[i].attribute_key)"
:operators="getOperators(appliedFilters[i].attribute_key)"
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
:show-query-operator="i !== appliedFilters.length - 1"
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
:grouped-filters="true"
:v="$v.appliedFilters.$each[i]"
@resetFilter="resetFilter(i, appliedFilters[i])"
@removeFilter="removeFilter(i)"
@ -48,22 +49,24 @@
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { required, requiredIf } from 'vuelidate/lib/validators';
import FilterInputBox from '../FilterInput.vue';
import FilterInputBox from '../FilterInput/Index.vue';
import languages from './advancedFilterItems/languages';
import countries from '/app/javascript/shared/constants/countries.js';
import countries from 'shared/constants/countries.js';
import { mapGetters } from 'vuex';
import { filterAttributeGroups } from './advancedFilterItems';
import filterMixin from 'shared/mixins/filterMixin';
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
export default {
components: {
FilterInputBox,
},
mixins: [alertMixin],
mixins: [alertMixin, filterMixin],
props: {
onClose: {
type: Function,
default: () => {},
},
filterTypes: {
initialFilterTypes: {
type: Array,
default: () => [],
},
@ -87,22 +90,21 @@ export default {
return {
show: true,
appliedFilters: [],
filterTypes: this.initialFilterTypes,
filterAttributeGroups,
filterGroups: [],
allCustomAttributes: [],
attributeModel: 'conversation_attribute',
filtersFori18n: 'FILTER',
};
},
computed: {
filterAttributes() {
return this.filterTypes.map(type => {
return {
key: type.attributeKey,
name: this.$t(`FILTER.ATTRIBUTES.${type.attributeI18nKey}`),
};
});
},
...mapGetters({
getAppliedConversationFilters: 'getAppliedConversationFilters',
}),
},
mounted() {
this.setFilterAttributes();
this.$store.dispatch('campaigns/get');
if (this.getAppliedConversationFilters.length) {
this.appliedFilters = [...this.getAppliedConversationFilters];
@ -112,10 +114,41 @@ export default {
filter_operator: 'equal_to',
values: '',
query_operator: 'and',
attribute_model: 'standard',
});
}
},
methods: {
getOperatorTypes(key) {
switch (key) {
case 'list':
return OPERATORS.OPERATOR_TYPES_1;
case 'text':
return OPERATORS.OPERATOR_TYPES_3;
case 'number':
return OPERATORS.OPERATOR_TYPES_1;
case 'link':
return OPERATORS.OPERATOR_TYPES_1;
case 'date':
return OPERATORS.OPERATOR_TYPES_4;
case 'checkbox':
return OPERATORS.OPERATOR_TYPES_1;
default:
return OPERATORS.OPERATOR_TYPES_1;
}
},
customAttributeInputType(key) {
switch (key) {
case 'date':
return 'date';
default:
return 'plain_text';
}
},
getAttributeModel(key) {
const type = this.filterTypes.find(filter => filter.attributeKey === key);
return type.attributeModel;
},
getInputType(key) {
const type = this.filterTypes.find(filter => filter.attributeKey === key);
return type.inputType;

View file

@ -128,7 +128,7 @@ export default {
type: [String, Number],
default: 0,
},
customViewsId: {
foldersId: {
type: [String, Number],
default: 0,
},
@ -246,7 +246,7 @@ export default {
id: chat.id,
label: this.activeLabel,
teamId: this.teamId,
customViewsId: this.customViewsId,
foldersId: this.foldersId,
conversationType: this.conversationType,
});
router.push({ path: frontendURL(path) });

View file

@ -57,22 +57,26 @@
/>
</div>
<spinner v-if="isPending" size="tiny" />
<a
v-if="isATweet && isIncoming && sender"
<div
v-if="showAvatar"
v-tooltip.top="tooltipForSender"
class="sender--info"
:href="twitterProfileLink"
target="_blank"
rel="noopener noreferrer nofollow"
>
<woot-thumbnail
:src="sender.thumbnail"
:username="sender.name"
:username="senderNameForAvatar"
size="16px"
/>
<div class="sender--available-name">
<a
v-if="isATweet && isIncoming"
class="sender--available-name"
:href="twitterProfileLink"
target="_blank"
rel="noopener noreferrer nofollow"
>
{{ sender.name }}
</div>
</a>
</a>
</div>
<div v-if="isFailed" class="message-failed--alert">
<woot-button
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
@ -113,7 +117,6 @@ import BubbleActions from './bubble/Actions';
import Spinner from 'shared/components/Spinner';
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
import { isEmptyObject } from 'dashboard/helper/commons';
import alertMixin from 'shared/mixins/alertMixin';
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
@ -242,6 +245,12 @@ export default {
isIncoming() {
return this.data.message_type === MESSAGE_TYPE.INCOMING;
},
isOutgoing() {
return this.data.message_type === MESSAGE_TYPE.OUTGOING;
},
isTemplate() {
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
},
emailHeadAttributes() {
return {
email: this.contentAttributes.email,
@ -258,6 +267,19 @@ export default {
hasText() {
return !!this.data.content;
},
tooltipForSender() {
const name = this.senderNameForAvatar;
const { message_type: messageType } = this.data;
const showTooltip =
messageType === MESSAGE_TYPE.OUTGOING ||
messageType === MESSAGE_TYPE.TEMPLATE;
return showTooltip
? {
content: `${this.$t('CONVERSATION.SENT_BY')} ${name}`,
classes: 'top',
}
: false;
},
messageToolTip() {
if (this.isMessageDeleted) {
return false;
@ -265,13 +287,7 @@ export default {
if (this.isFailed) {
return this.$t(`CONVERSATION.SEND_FAILED`);
}
const { sender } = this;
return this.data.message_type === 1 && !isEmptyObject(sender)
? {
content: `${this.$t('CONVERSATION.SENT_BY')} ${sender.name}`,
classes: 'top',
}
: false;
return false;
},
wrapClass() {
return {
@ -313,6 +329,19 @@ export default {
const { meta } = this.data;
return meta ? meta.error : '';
},
showAvatar() {
if (this.isOutgoing || this.isTemplate) {
return true;
}
return this.isATweet && this.isIncoming && this.sender;
},
senderNameForAvatar() {
if (this.isOutgoing || this.isTemplate) {
const { name = this.$t('CONVERSATION.BOT') } = this.sender || {};
return name;
}
return '';
},
},
watch: {
data() {

View file

@ -1,56 +1,29 @@
<template>
<div class="view-box fill-height">
<div
<banner
v-if="!currentChat.can_reply && !isAWhatsappChannel"
class="banner messenger-policy--banner"
>
<span>
{{ $t('CONVERSATION.CANNOT_REPLY') }}
<a
:href="facebookReplyPolicy"
rel="noopener noreferrer nofollow"
target="_blank"
>
{{ $t('CONVERSATION.24_HOURS_WINDOW') }}
</a>
</span>
</div>
<div
v-if="!currentChat.can_reply && isAWhatsappChannel"
class="banner messenger-policy--banner"
>
<span>
{{ $t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY') }}
<a
:href="twilioWhatsAppReplyPolicy"
rel="noopener noreferrer nofollow"
target="_blank"
>
{{ $t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW') }}
</a>
</span>
</div>
color-scheme="alert"
:banner-message="$t('CONVERSATION.CANNOT_REPLY')"
:href-link="facebookReplyPolicy"
:href-link-text="$t('CONVERSATION.24_HOURS_WINDOW')"
/>
<banner
v-if="!currentChat.can_reply && isAWhatsappChannel"
color-scheme="alert"
:banner-message="$t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY')"
:href-link="twilioWhatsAppReplyPolicy"
:href-link-text="$t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW')"
/>
<banner
v-if="isATweet"
color-scheme="gray"
:banner-message="tweetBannerText"
:has-close-button="hasSelectedTweetId"
@close="removeTweetSelection"
/>
<div v-if="isATweet" class="banner">
<span v-if="!selectedTweetId">
{{ $t('CONVERSATION.SELECT_A_TWEET_TO_REPLY') }}
</span>
<span v-else>
{{ $t('CONVERSATION.REPLYING_TO') }}
{{ selectedTweet.content || '' }}
</span>
<button
v-if="selectedTweetId"
class="banner-close-button"
@click="removeTweetSelection"
>
<fluent-icon
v-tooltip="$t('CONVERSATION.REMOVE_SELECTION')"
size="16"
icon="dismiss"
/>
</button>
</div>
<div class="sidebar-toggle__wrap">
<woot-button
variant="smooth"
@ -126,6 +99,7 @@ import { mapGetters } from 'vuex';
import ReplyBox from './ReplyBox';
import Message from './Message';
import conversationMixin from '../../../mixins/conversations';
import Banner from 'dashboard/components/ui/Banner.vue';
import { getTypingUsersText } from '../../../helper/commons';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { REPLY_POLICY } from 'shared/constants/links';
@ -139,6 +113,7 @@ export default {
components: {
Message,
ReplyBox,
Banner,
},
mixins: [conversationMixin, inboxMixin, eventListenerMixins, clickaway],
props: {
@ -173,7 +148,17 @@ export default {
inbox() {
return this.$store.getters['inboxes/getInbox'](this.inboxId);
},
hasSelectedTweetId() {
return !!this.selectedTweetId;
},
tweetBannerText() {
return !this.selectedTweetId
? this.$t('CONVERSATION.SELECT_A_TWEET_TO_REPLY')
: `
${this.$t('CONVERSATION.REPLYING_TO')}
${this.selectedTweet.content}` || '';
},
typingUsersList() {
const userList = this.$store.getters[
'conversationTypingStatus/getUserList'
@ -375,31 +360,6 @@ export default {
</script>
<style scoped lang="scss">
.banner {
background: var(--b-500);
color: var(--white);
font-size: var(--font-size-mini);
padding: var(--space-slab) var(--space-normal);
text-align: center;
position: relative;
a {
text-decoration: underline;
color: var(--white);
font-size: var(--font-size-mini);
}
&.messenger-policy--banner {
background: var(--r-400);
}
.banner-close-button {
cursor: pointer;
margin-left: var(--space--two);
color: var(--white);
}
}
.spinner--container {
min-height: var(--space-jumbo);
}

View file

@ -1,5 +1,13 @@
<template>
<div class="reply-box" :class="replyBoxClass">
<banner
v-if="showSelfAssignBanner"
color-scheme="secondary"
:banner-message="$t('CONVERSATION.NOT_ASSIGNED_TO_YOU')"
:has-action-button="true"
:action-button-label="$t('CONVERSATION.ASSIGN_TO_ME')"
@click="onClickSelfAssign"
/>
<reply-top-panel
:mode="replyType"
:set-reply-mode="setReplyMode"
@ -61,7 +69,7 @@
<reply-bottom-panel
:mode="replyType"
:send-button-text="replyButtonLabel"
:on-file-upload="onFileUpload"
:on-direct-file-upload="onDirectFileUpload"
:show-file-upload="showFileUpload"
:toggle-emoji-picker="toggleEmojiPicker"
:show-emoji-picker="showEmojiPicker"
@ -72,6 +80,7 @@
:is-format-mode="showRichContentEditor"
:enable-rich-editor="isRichEditorEnabled"
:enter-to-send-enabled="enterToSendEnabled"
:enable-multiple-file-upload="enableMultipleFileUpload"
@toggleEnterToSend="toggleEnterToSend"
/>
</div>
@ -89,6 +98,7 @@ import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel';
import ReplyEmailHead from './ReplyEmailHead';
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel';
import Banner from 'dashboard/components/ui/Banner.vue';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
@ -99,10 +109,12 @@ import {
isEscape,
isEnter,
hasPressedShift,
hasPressedCommandPlusKKey,
} from 'shared/helpers/KeyboardHelpers';
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
import inboxMixin from 'shared/mixins/inboxMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { DirectUpload } from 'activestorage';
export default {
components: {
@ -114,6 +126,7 @@ export default {
ReplyEmailHead,
ReplyBottomPanel,
WootMessageEditor,
Banner,
},
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
props: {
@ -147,6 +160,11 @@ export default {
};
},
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
currentUser: 'getCurrentUser',
}),
showRichContentEditor() {
if (this.isOnPrivateNote) {
return true;
@ -161,7 +179,36 @@ export default {
}
return false;
},
...mapGetters({ currentChat: 'getSelectedChat' }),
assignedAgent: {
get() {
return this.currentChat.meta.assignee;
},
set(agent) {
const agentId = agent ? agent.id : 0;
this.$store.dispatch('setCurrentChatAssignee', agent);
this.$store
.dispatch('assignAgent', {
conversationId: this.currentChat.id,
agentId,
})
.then(() => {
this.showAlert(this.$t('CONVERSATION.CHANGE_AGENT'));
});
},
},
showSelfAssignBanner() {
if (this.message !== '' && !this.isOnPrivateNote) {
if (!this.assignedAgent) {
return true;
}
if (this.assignedAgent.id !== this.currentUser.id) {
return true;
}
}
return false;
},
enterToSendEnabled() {
return !!this.uiSettings.enter_to_send_enabled;
},
@ -283,6 +330,9 @@ export default {
showReplyHead() {
return !this.isOnPrivateNote && this.isAnEmailChannel;
},
enableMultipleFileUpload() {
return this.isAnEmailChannel || this.isAWebWidgetInbox || this.isAPIInbox;
},
},
watch: {
currentChat(conversation) {
@ -355,11 +405,40 @@ export default {
e.preventDefault();
this.sendMessage();
}
} else if (hasPressedCommandPlusKKey(e)) {
this.openCommandBar();
}
},
openCommandBar() {
const ninja = document.querySelector('ninja-keys');
ninja.open();
},
toggleEnterToSend(enterToSendEnabled) {
this.updateUISettings({ enter_to_send_enabled: enterToSendEnabled });
},
onClickSelfAssign() {
const {
account_id,
availability_status,
available_name,
email,
id,
name,
role,
avatar_url,
} = this.currentUser;
const selfAssign = {
account_id,
availability_status,
available_name,
email,
id,
name,
role,
thumbnail: avatar_url,
};
this.assignedAgent = selfAssign;
},
async sendMessage() {
if (this.isReplyButtonDisabled) {
return;
@ -439,6 +518,38 @@ export default {
isPrivate,
});
},
onDirectFileUpload(file) {
if (!file) {
return;
}
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
const upload = new DirectUpload(
file.file,
'/rails/active_storage/direct_uploads',
null,
file.file.name
);
upload.create((error, blob) => {
if (error) {
this.showAlert(error);
} else {
this.attachedFiles.push({
currentChatId: this.currentChat.id,
resource: blob,
isPrivate: this.isPrivate,
thumb: null,
blobSignedId: blob.signed_id,
});
}
});
} else {
this.showAlert(
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
MAXIMUM_FILE_UPLOAD_SIZE,
})
);
}
},
onFileUpload(file) {
if (!file) {
return;
@ -469,7 +580,6 @@ export default {
);
},
getMessagePayload(message) {
const [attachment] = this.attachedFiles;
const messagePayload = {
conversationId: this.currentChat.id,
message,
@ -480,8 +590,11 @@ export default {
messagePayload.contentAttributes = { in_reply_to: this.inReplyTo };
}
if (attachment) {
messagePayload.file = attachment.resource.file;
if (this.attachedFiles && this.attachedFiles.length) {
messagePayload.files = [];
this.attachedFiles.forEach(attachment => {
messagePayload.files.push(attachment.blobSignedId);
});
}
if (this.ccEmails) {

View file

@ -1,229 +1,144 @@
import {
OPERATOR_TYPES_1,
OPERATOR_TYPES_2,
OPERATOR_TYPES_3,
} from '../../FilterInput/FilterOperatorTypes';
const filterTypes = [
{
attributeKey: 'status',
attributeI18nKey: 'STATUS',
inputType: 'multi_select',
dataType: 'text',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_1,
attributeModel: 'standard',
},
{
attributeKey: 'assignee_id',
attributeI18nKey: 'ASSIGNEE_NAME',
inputType: 'search_select',
dataType: 'text',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_2,
attributeModel: 'standard',
},
{
attributeKey: 'inbox_id',
attributeI18nKey: 'INBOX_NAME',
inputType: 'search_select',
dataType: 'text',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_2,
attributeModel: 'standard',
},
{
attributeKey: 'team_id',
attributeI18nKey: 'TEAM_NAME',
inputType: 'search_select',
dataType: 'number',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_2,
attributeModel: 'standard',
},
{
attributeKey: 'display_id',
attributeI18nKey: 'CONVERSATION_IDENTIFIER',
inputType: 'plain_text',
dataType: 'Number',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'contains',
label: 'Contains',
},
{
value: 'does_not_contain',
label: 'Does not contain',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_3,
attributeModel: 'standard',
},
{
attributeKey: 'campaign_id',
attributeI18nKey: 'CAMPAIGN_NAME',
inputType: 'search_select',
dataType: 'Number',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_2,
attributeModel: 'standard',
},
{
attributeKey: 'labels',
attributeI18nKey: 'LABELS',
inputType: 'multi_select',
dataType: 'text',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
{
value: 'is_present',
label: 'Is present',
},
{
value: 'is_not_present',
label: 'Is not present',
},
],
attribute_type: 'standard',
filterOperators: OPERATOR_TYPES_2,
attributeModel: 'standard',
},
{
attributeKey: 'browser_language',
attributeI18nKey: 'BROWSER_LANGUAGE',
inputType: 'search_select',
dataType: 'text',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
],
attribute_type: 'additional_attributes',
filterOperators: OPERATOR_TYPES_1,
attributeModel: 'additional',
},
{
attributeKey: 'country_code',
attributeI18nKey: 'COUNTRY_NAME',
inputType: 'search_select',
dataType: 'text',
filterOperators: [
{
value: 'equal_to',
label: 'Equal to',
},
{
value: 'not_equal_to',
label: 'Not equal to',
},
],
attribute_type: 'additional_attributes',
filterOperators: OPERATOR_TYPES_1,
attributeModel: 'additional',
},
{
attributeKey: 'referer',
attributeI18nKey: 'REFERER_LINK',
inputType: 'plain_text',
dataType: 'text',
filterOperators: [
filterOperators: OPERATOR_TYPES_3,
attributeModel: 'additional',
},
];
export const filterAttributeGroups = [
{
name: 'Standard Filters',
i18nGroup: 'STANDARD_FILTERS',
attributes: [
{
value: 'equal_to',
label: 'Equal to',
key: 'status',
i18nKey: 'STATUS',
},
{
value: 'not_equal_to',
label: 'Not equal to',
key: 'assignee_id',
i18nKey: 'ASSIGNEE_NAME',
},
{
value: 'contains',
label: 'Contains',
key: 'inbox_id',
i18nKey: 'INBOX_NAME',
},
{
value: 'does_not_contain',
label: 'Does not contain',
key: 'team_id',
i18nKey: 'TEAM_NAME',
},
{
key: 'display_id',
i18nKey: 'CONVERSATION_IDENTIFIER',
},
{
key: 'campaign_id',
i18nKey: 'CAMPAIGN_NAME',
},
{
key: 'labels',
i18nKey: 'LABELS',
},
],
},
{
name: 'Additional Filters',
i18nGroup: 'ADDITIONAL_FILTERS',
attributes: [
{
key: 'browser_language',
i18nKey: 'BROWSER_LANGUAGE',
},
{
key: 'country_code',
i18nKey: 'COUNTRY_NAME',
},
{
key: 'referer',
i18nKey: 'REFERER_LINK',
},
],
attribute_type: 'additional_attributes',
},
];

View file

@ -12,7 +12,7 @@ export const conversationUrl = ({
label,
teamId,
conversationType = '',
customViewsId,
foldersId,
}) => {
let url = `accounts/${accountId}/conversations/${id}`;
if (activeInbox) {
@ -21,8 +21,8 @@ export const conversationUrl = ({
url = `accounts/${accountId}/label/${label}/conversations/${id}`;
} else if (teamId) {
url = `accounts/${accountId}/team/${teamId}/conversations/${id}`;
} else if (customViewsId && customViewsId !== 0) {
url = `accounts/${accountId}/custom_view/${customViewsId}/conversations/${id}`;
} else if (foldersId && foldersId !== 0) {
url = `accounts/${accountId}/custom_view/${foldersId}/conversations/${id}`;
} else if (conversationType === 'mention') {
url = `accounts/${accountId}/mentions/conversations/${id}`;
}
@ -37,3 +37,9 @@ export const accountIdFromPathname = pathname => {
const accountId = isScoped ? Number(urlParam) : '';
return accountId;
};
export const isValidURL = value => {
/* eslint-disable no-useless-escape */
const URL_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/gm;
return URL_REGEX.test(value);
};

View file

@ -0,0 +1,18 @@
const generatePayload = data => {
const actions = JSON.parse(JSON.stringify(data));
let payload = actions.map(item => {
if (Array.isArray(item.action_params)) {
item.action_params = item.action_params.map(val => val.id);
} else if (typeof item.values === 'object') {
item.action_params = [item.action_params.id];
} else if (!item.action_params) {
item.action_params = [];
} else {
item.action_params = [item.action_params];
}
return item;
});
return payload;
};
export default generatePayload;

View file

@ -1,5 +1,19 @@
const lowerCaseValues = (operator, values) => {
if (operator === 'equal_to' || operator === 'not_equal_to') {
values = values.map(val => {
if (typeof val === 'string') {
return val.toLowerCase();
}
return val;
});
}
return values;
};
const generatePayload = data => {
let payload = data.map(item => {
// Make a copy of data to avoid vue data reactivity issues
const filters = JSON.parse(JSON.stringify(data));
let payload = filters.map(item => {
if (Array.isArray(item.values)) {
item.values = item.values.map(val => val.id);
} else if (typeof item.values === 'object') {
@ -9,6 +23,8 @@ const generatePayload = data => {
} else {
item.values = [item.values];
}
// Convert all values to lowerCase if operator_type is 'equal_to' or 'not_equal_to'
item.values = lowerCaseValues(item.filter_operator, item.values);
return item;
});
// For every query added, the query_operator is set default to and so the

View file

@ -2,6 +2,7 @@ import {
frontendURL,
conversationUrl,
accountIdFromPathname,
isValidURL,
} from '../URLHelper';
describe('#URL Helpers', () => {
@ -48,4 +49,13 @@ describe('#URL Helpers', () => {
expect(accountIdFromPathname('')).toBe('');
});
});
describe('isValidURL', () => {
it('should return true if valid url is passed', () => {
expect(isValidURL('https://chatwoot.com')).toBe(true);
});
it('should return false if invalid url is passed', () => {
expect(isValidURL('alert.window')).toBe(false);
});
});
});

View file

@ -0,0 +1,41 @@
import actionQueryGenerator from '../actionQueryGenerator';
const testData = [
{
action_name: 'add_label',
action_params: [{ id: 'testlabel', name: 'testlabel' }],
},
{
action_name: 'assign_team',
action_params: [
{
id: 1,
name: 'sales team',
description: 'This is our internal sales team',
allow_auto_assign: true,
account_id: 1,
is_member: true,
},
],
},
];
const finalResult = [
{
action_name: 'add_label',
action_params: ['testlabel'],
},
{
action_name: 'assign_team',
action_params: [1],
},
];
describe('#actionQueryGenerator', () => {
it('returns the correct format of filter query', () => {
expect(actionQueryGenerator(testData)).toEqual(finalResult);
expect(
actionQueryGenerator(testData).every(i => Array.isArray(i.action_params))
).toBe(true);
});
});

View file

@ -5,7 +5,7 @@ const testData = [
attribute_key: 'status',
filter_operator: 'equal_to',
values: [
{ id: 'pending', name: 'Pending' },
{ id: 'PENDING', name: 'Pending' },
{ id: 'resolved', name: 'Resolved' },
],
query_operator: 'and',
@ -52,7 +52,7 @@ const finalResult = {
{
attribute_key: 'id',
filter_operator: 'equal_to',
values: ['This is a test'],
values: ['this is a test'],
},
],
};

View file

@ -1,6 +1,6 @@
{
"FILTER": {
"TITLE": "تصفية المحادثة",
"TITLE": "تصفية المحادثات",
"SUBTITLE": "إضافة فلاتر أدناه واضغط على 'إرسال' لتصفية المحادثات.",
"ADD_NEW_FILTER": "إضافة فلتر",
"FILTER_DELETE_ERROR": "يجب ان يكون لديك فلتر واحد على الاقل",
@ -19,7 +19,9 @@
"contains": "يحتوي",
"does_not_contain": "لا يحتوي",
"is_present": "موجود",
"is_not_present": "غير موجود"
"is_not_present": "غير موجود",
"is_greater_than": "هو أكبر من",
"is_lesser_than": "هو أقل من"
},
"ATTRIBUTES": {
"STATUS": "الحالة",
@ -31,7 +33,54 @@
"LABELS": "الوسوم",
"BROWSER_LANGUAGE": "لغة المتصفح",
"COUNTRY_NAME": "اسم الدولة",
"REFERER_LINK": "رابط المرجع"
"REFERER_LINK": "رابط المرجع",
"CUSTOM_ATTRIBUTE_LIST": "القائمة",
"CUSTOM_ATTRIBUTE_TEXT": "النص",
"CUSTOM_ATTRIBUTE_NUMBER": "العدد",
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع"
},
"GROUPS": {
"STANDARD_FILTERS": "الفلاتر القياسية",
"ADDITIONAL_FILTERS": "فلاتر إضافية",
"CUSTOM_ATTRIBUTES": "سمات مخصصة"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "هل تريد حفظ هذا الفلتر؟",
"LABEL": "تسمية هذا الفلتر",
"PLACEHOLDER": "أدخل اسم لهذا الفلتر",
"ERROR_MESSAGE": "الاسم مطلوب",
"SAVE_BUTTON": "حفظ الفلتر",
"CANCEL_BUTTON": "إلغاء",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح",
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح",
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة"
}
},
"DELETE": {
"DELETE_BUTTON": "حذف الفلتر",
"MODAL": {
"CONFIRM": {
"TITLE": "تأكيد الحذف",
"MESSAGE": "هل أنت متأكد من حذف الفلتر ",
"YES": "نعم، احذف",
"NO": "لا، احتفظ به"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "تم حذف طريقة عرض مخصصة بنجاح",
"ERROR_MESSAGE": "حدث خطأ أثناء حذف المجلد"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "تم حذف العرض المخصص بنجاح",
"ERROR_MESSAGE": "حدث خطأ أثناء حذف طريقة عرض مخصصة"
}
}
}
}
}

View file

@ -1,6 +1,89 @@
{
"AUTOMATION": {
"HEADER": "الأتمتة",
"HEADER_BTN_TXT": "إضافة قاعدة أتمتة"
"HEADER_BTN_TXT": "إضافة قاعدة أتمتة",
"LOADING": "جلب قواعد الأتمتة",
"SIDEBAR_TXT": "<p><b>قواعد الأتمتة الآليه</b> <p>يمكن للأتمتة استبدال وأتمتة العمليات القائمة التي تتطلب جهداً يدوياً. يمكنك القيام بالعديد من الأشياء مع التشغيل الآلي، بما في ذلك إضافة تسميات وتعيين المحادثة لأفضل وكيل. لذا يركز الفريق على ما يفعلونه على أفضل وجه ويقضي وقتاً قليلاً على المهام اليدوية.</p>",
"ADD": {
"TITLE": "إضافة قاعدة أتمتة",
"SUBMIT": "إنشاء",
"CANCEL_BUTTON_TEXT": "إلغاء",
"FORM": {
"NAME": {
"LABEL": "اسم القاعدة",
"PLACEHOLDER": "أدخل اسم القاعدة",
"ERROR": "الاسم مطلوب"
},
"DESC": {
"LABEL": "الوصف",
"PLACEHOLDER": "ادخل وصف القاعدة",
"ERROR": "الوصف مطلوب"
},
"EVENT": {
"LABEL": "الحدث",
"PLACEHOLDER": "الرجاء اختيار واحد",
"ERROR": "الحدث مطلوب"
},
"CONDITIONS": {
"LABEL": "الشروط"
},
"ACTIONS": {
"LABEL": "الإجراءات"
}
},
"CONDITION_BUTTON_LABEL": "إضافة شرط",
"ACTION_BUTTON_LABEL": "إضافة إجراء",
"API": {
"SUCCESS_MESSAGE": "تمت إضافة قاعدة الأتمتة بنجاح",
"ERROR_MESSAGE": "تعذر إنشاء قاعدة أتمتة ، يرجى المحاولة مرة أخرى لاحقاً"
}
},
"LIST": {
"TABLE_HEADER": [
"الاسم",
"الوصف",
"مفعل",
"تم إنشاؤها في"
],
"404": "لم يتم العثور على قواعد أتمتة"
},
"DELETE": {
"TITLE": "حذف قاعدة الأتمتة",
"SUBMIT": "حذف",
"CANCEL_BUTTON_TEXT": "إلغاء",
"CONFIRM": {
"TITLE": "تأكيد الحذف",
"MESSAGE": "هل أنت متأكد من الحذف ",
"YES": "نعم، احذف ",
"NO": "لا، احتفظ "
},
"API": {
"SUCCESS_MESSAGE": "تم حذف قاعدة الأتمتة بنجاح",
"ERROR_MESSAGE": "تعذر حذف قاعدة الأتمتة، يرجى المحاولة مرة أخرى لاحقاً"
}
},
"EDIT": {
"TITLE": "تعديل قاعدة الأتمتة",
"SUBMIT": "تعديل",
"CANCEL_BUTTON_TEXT": "إلغاء",
"API": {
"SUCCESS_MESSAGE": "تم تحديث قاعدة الأتمتة بنجاح",
"ERROR_MESSAGE": "تعذر تحديث قاعدة الأتمتة، الرجاء المحاولة مرة أخرى في وقت لاحق"
}
},
"CLONE": {
"TOOLTIP": "نسخ",
"API": {
"SUCCESS_MESSAGE": "تم نسخ الأتمتة بنجاح",
"ERROR_MESSAGE": "تعذر استنساخ قاعدة الأتمتة، الرجاء المحاولة مرة أخرى لاحقاً"
}
},
"FORM": {
"EDIT": "تعديل",
"CREATE": "إنشاء",
"DELETE": "حذف",
"CANCEL": "إلغاء",
"RESET_MESSAGE": "تغيير نوع الحدث سوف يعيد تعيين الشروط والأحداث التي أضفتها أدناه"
}
}
}

View file

@ -17,7 +17,7 @@
},
"ADD": {
"TITLE": "إضافة رد جاهز",
"DESC": "الردود الجاهزة هي قوالب رسائل معدة مسبقاً يمكن استخدامها لتسريع كتابة الردود في المحادثات .",
"DESC": "الردود الجاهزة هي قوالب رسائل معدة مسبقاً يمكن استخدامها لتسريع كتابة الردود في المحادثات.",
"CANCEL_BUTTON_TEXT": "إلغاء",
"FORM": {
"SHORT_CODE": {

View file

@ -7,7 +7,7 @@
"404": "لا توجد محادثات نشطة في هذه المجموعة."
},
"TAB_HEADING": "المحادثات",
"MENTION_HEADING": "Mentions",
"MENTION_HEADING": "الإشارات",
"SEARCH": {
"INPUT": "البحث عن جهات الاتصال، المحادثات، قوالب الردود الجاهزة .."
},

View file

@ -111,7 +111,7 @@
"LABEL": "رقم الهاتف",
"HELP": "يجب ان يحتوى رقم الهاتف على كود دولتك تسبقها علامة +\nمثال: +20101243567",
"ERROR": "يجب ان تكون خانة رقم الهاتف إما فارغة او مكتملة مع رمز الدولة",
"DUPLICATE": "This phone number is in use for another contact."
"DUPLICATE": "رقم الهاتف هذا مستخدم لجهة اتصال أخرى."
},
"LOCATION": {
"PLACEHOLDER": "أدخل موقع جهة الاتصال",
@ -169,6 +169,7 @@
"SUBMIT": "إرسال الرسالة",
"CANCEL": "إلغاء",
"SUCCESS_MESSAGE": "تم إرسال الرسالة!",
"GO_TO_CONVERSATION": "عرض",
"ERROR_MESSAGE": "تعذر الإرسال! حاول مرة أخرى"
}
},
@ -177,7 +178,9 @@
"FIELDS": "تصنفيات جهات الاتصال",
"SEARCH_BUTTON": "بحث",
"SEARCH_INPUT_PLACEHOLDER": "بحث عن جهات الاتصال",
"FILTER_CONTACTS": "Filter",
"FILTER_CONTACTS": "فلترة",
"FILTER_CONTACTS_SAVE": "حفظ الفلتر",
"FILTER_CONTACTS_DELETE": "حذف الفلتر",
"LIST": {
"LOADING_MESSAGE": "جاري تحميل جهات الاتصال...",
"404": "لا توجد جهات اتصال تطابق بحثك 🔍",

View file

@ -1,15 +1,15 @@
{
"CONTACTS_FILTER": {
"TITLE": "Filter Contacts",
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
"TITLE": "تصفية جهات الاتصال",
"SUBTITLE": "إضافة فلاتر أدناه واضغط على 'إرسال' لتصفية جهات الاتصال.",
"ADD_NEW_FILTER": "إضافة فلتر",
"CLEAR_ALL_FILTERS": "Clear All Filters",
"CLEAR_ALL_FILTERS": "مسح جميع الفلاتر",
"FILTER_DELETE_ERROR": "يجب ان يكون لديك فلتر واحد على الاقل",
"SUBMIT_BUTTON_LABEL": "إرسال",
"CANCEL_BUTTON_LABEL": "إلغاء",
"CLEAR_BUTTON_LABEL": "مسح الفلاتر",
"EMPTY_VALUE_ERROR": "القيمة مطلوبة",
"TOOLTIP_LABEL": "Filter contacts",
"TOOLTIP_LABEL": "تصفية جهات الاتصال",
"QUERY_DROPDOWN_LABELS": {
"AND": "و",
"OR": "أو"
@ -20,15 +20,27 @@
"contains": "يحتوي",
"does_not_contain": "لا يحتوي",
"is_present": "موجود",
"is_not_present": "غير موجود"
"is_not_present": "غير موجود",
"is_greater_than": "هو أكبر من",
"is_lesser_than": "هو أقل من"
},
"ATTRIBUTES": {
"NAME": "الاسم",
"EMAIL": "البريد الإلكتروني",
"PHONE_NUMBER": "رقم الهاتف",
"IDENTIFIER": "Identifier",
"IDENTIFIER": "المعرف",
"CITY": "المدينة",
"COUNTRY": "الدولة"
"COUNTRY": "الدولة",
"CUSTOM_ATTRIBUTE_LIST": "القائمة",
"CUSTOM_ATTRIBUTE_TEXT": "النص",
"CUSTOM_ATTRIBUTE_NUMBER": "العدد",
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع"
},
"GROUPS": {
"STANDARD_FILTERS": "الفلاتر القياسية",
"ADDITIONAL_FILTERS": "فلاتر إضافية",
"CUSTOM_ATTRIBUTES": "سمات مخصصة"
}
}
}

View file

@ -22,6 +22,8 @@
"LOADING_CONVERSATIONS": "جاري تحميل المحادثات",
"CANNOT_REPLY": "لا يمكنك الرد بسبب",
"24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
"NOT_ASSIGNED_TO_YOU": "لم يتم تعيين هذه المحادثة لك. هل ترغب في تعيين هذه المحادثة لنفسك؟",
"ASSIGN_TO_ME": "إسناد لي",
"TWILIO_WHATSAPP_CAN_REPLY": "يمكنك فقط الرد على هذه المحادثة باستخدام رسالة قالب بسبب",
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
"SELECT_A_TWEET_TO_REPLY": "الرجاء تحديد تغريدة للرد عليها.",
@ -90,6 +92,9 @@
"FILE_SIZE_LIMIT": "حجم الملف يتجاوز حد الاقصى وهو {MAXIMUM_FILE_UPLOAD_SIZE}",
"MESSAGE_ERROR": "غير قادر على إرسال هذه الرسالة، الرجاء المحاولة مرة أخرى لاحقاً",
"SENT_BY": "أرسلت بواسطة:",
"BOT": "رد آلي",
"SEND_FAILED": "تعذر إرسال الرسالة! حاول مرة أخرى",
"TRY_AGAIN": "إعادة المحاولة",
"ASSIGNMENT": {
"SELECT_AGENT": "اختر وكيل",
"REMOVE": "حذف",

View file

@ -14,8 +14,8 @@
"NOTE": ""
},
"ACCOUNT_ID": {
"TITLE": "Account ID",
"NOTE": "This ID is required if you are building an API based integration"
"TITLE": "معرف الحساب",
"NOTE": "هذا المعرف مطلوب إذا كنت بصدد بناء تكامل على API"
},
"NAME": {
"LABEL": "اسم الحساب",
@ -40,7 +40,7 @@
"AUTO_RESOLVE_DURATION": {
"LABEL": "عدد الأيام بعد التذكرة التي يجب أن يحل تلقائياً إذا لم يكن هناك أي نشاط",
"PLACEHOLDER": "30",
"ERROR": "الرجاء إدخال مدة الحل التلقائي صحيحة (يوم واحد على الأقل)"
"ERROR": "الرجاء إدخال مدة حل تلقائي صالحة (حد أدنى 1 يوم والحد الأقصى 999 يوما)"
},
"FEATURES": {
"INBOUND_EMAIL_ENABLED": "الاستمرار في المحادثة عبر رسائل البريد الإلكتروني مفعّل لحسابك.",

View file

@ -49,7 +49,7 @@
"HELP": "لإضافة حساب تويتر الخاص بك كقناة تواصل، تحتاج إلى مصادقة حسابك على تويتر بك بالنقر على زر \"تسجيل الدخول باستخدام تويتر\" ",
"ERROR_MESSAGE": "حدث خطأ أثناء الاتصال بـ Twitter، الرجاء المحاولة مرة أخرى",
"TWEETS": {
"ENABLE": "Create conversations from mentioned Tweets"
"ENABLE": "إنشاء محادثات من التغريدات المشار إليها"
}
},
"WEBSITE_CHANNEL": {
@ -136,8 +136,56 @@
}
},
"SMS": {
"TITLE": "قناة SMS عبر Twilio",
"DESC": "ابدأ في دعم عملائك عبر الرسائل القصيرة بإستخدام Twilio."
"TITLE": "قناة SMS",
"DESC": "ابدأ في دعم عملائك عبر الرسائل القصيرة.",
"PROVIDERS": {
"LABEL": "API Provider",
"TWILIO": "تويليو",
"BANDWIDTH": "سعة الإنترنت"
},
"API": {
"ERROR_MESSAGE": "لم نتمكن من حفظ قناة الرسائل القصيرة"
},
"BANDWIDTH": {
"ACCOUNT_ID": {
"LABEL": "معرف الحساب",
"PLACEHOLDER": "الرجاء إدخال معرف حساب النطاق الترددي الخاص بك",
"ERROR": "هذا الحقل مطلوب"
},
"API_KEY": {
"LABEL": "مفتاح API",
"PLACEHOLDER": "الرجاء إدخال مفتاح API الخاص بك",
"ERROR": "هذا الحقل مطلوب"
},
"API_SECRET": {
"LABEL": "سرية API",
"PLACEHOLDER": "الرجاء إدخال مفتاح API الخاص بك",
"ERROR": "هذا الحقل مطلوب"
},
"APPLICATION_ID": {
"LABEL": "معرف التطبيق",
"PLACEHOLDER": "الرجاء إدخال معرف تطبيق النطاق الترددي الخاص بك",
"ERROR": "هذا الحقل مطلوب"
},
"INBOX_NAME": {
"LABEL": "اسم صندوق الوارد لقناة التواصل",
"PLACEHOLDER": "الرجاء إدخال اسم القناة",
"ERROR": "هذا الحقل مطلوب"
},
"PHONE_NUMBER": {
"LABEL": "رقم الهاتف",
"PLACEHOLDER": "الرجاء إدخال رقم الهاتف الذي سيتم إرسال الرسائل منه.",
"ERROR": "الرجاء إدخال قيمة صحيحة. يجب أن يبدأ رقم الهاتف بعلامة `+`."
},
"SUBMIT_BUTTON": "إنشاء قناة عرض التردد",
"API": {
"ERROR_MESSAGE": "تعذر تكوين المصادقة بواسطة بيانات الاعتماد الخاصة بحسابك على Twilio، يرجى المحاولة مرة أخرى"
},
"API_CALLBACK": {
"TITLE": "عنوان Callback URL",
"SUBTITLE": "يتوجب تهيئة عنوان callback URL في إعدادات Twilio بإدخال القيمة أدناه."
}
}
},
"WHATSAPP": {
"TITLE": "قناة واتساب",
@ -305,6 +353,14 @@
"ENABLED": "مفعل",
"DISABLED": "معطّل"
},
"ALLOW_MESSAGES_AFTER_RESOLVED": {
"ENABLED": "مفعل",
"DISABLED": "معطّل"
},
"ENABLE_CONTINUITY_VIA_EMAIL": {
"ENABLED": "مفعل",
"DISABLED": "معطّل"
},
"ENABLE_HMAC": {
"LABEL": "تمكين"
}
@ -351,6 +407,8 @@
"AUTO_ASSIGNMENT": "تفعيل الإسناد التلقائي",
"ENABLE_CSAT": "تمكين تقييم خدمة العملاء",
"ENABLE_CSAT_SUB_TEXT": "تمكين/تعطيل تقييم خدمة العملاء بعد إنتهاء المحادثة",
"ENABLE_CONTINUITY_VIA_EMAIL": "تمكين استمرارية المحادثة عبر البريد الإلكتروني",
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "المحادثات ستستمر عبر البريد الإلكتروني إذا كان عنوان البريد الإلكتروني لجهة الاتصال متاحاً.",
"INBOX_UPDATE_TITLE": "إعدادات قناة التواصل",
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.",
@ -361,7 +419,9 @@
"INBOX_IDENTIFIER": "معرف صندوق الوارد",
"INBOX_IDENTIFIER_SUB_TEXT": "استخدم رمز 'inbox_identifier' المعروض هنا للمصادقة على عملاء API الخاص بك.",
"FORWARD_EMAIL_TITLE": "إعادة التوجيه إلى البريد الإلكتروني",
"FORWARD_EMAIL_SUB_TEXT": "بدء إعادة توجيه رسائل البريد الإلكتروني الخاصة بك إلى عنوان البريد الإلكتروني التالي."
"FORWARD_EMAIL_SUB_TEXT": "بدء إعادة توجيه رسائل البريد الإلكتروني الخاصة بك إلى عنوان البريد الإلكتروني التالي.",
"ALLOW_MESSAGES_AFTER_RESOLVED": "السماح بالرسائل بعد حل المحادثة",
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "السماح للمستخدمين النهائيين بإرسال رسائل حتى بعد تسوية المحادثة."
},
"FACEBOOK_REAUTHORIZE": {
"TITLE": "إعادة التصريح",

View file

@ -1,9 +1,12 @@
import { default as _advancedFilters } from './advancedFilters.json';
import { default as _agentMgmt } from './agentMgmt.json';
import { default as _attributesMgmt } from './attributesMgmt.json';
import { default as _automation } from './automation.json';
import { default as _campaign } from './campaign.json';
import { default as _cannedMgmt } from './cannedMgmt.json';
import { default as _chatlist } from './chatlist.json';
import { default as _contact } from './contact.json';
import { default as _contactFilters } from './contactFilters.json';
import { default as _conversation } from './conversation.json';
import { default as _csatMgmtMgmt } from './csatMgmt.json';
import { default as _generalSettings } from './generalSettings.json';
@ -20,12 +23,15 @@ import { default as _signup } from './signup.json';
import { default as _teamsSettings } from './teamsSettings.json';
export default {
..._advancedFilters,
..._agentMgmt,
..._attributesMgmt,
..._automation,
..._campaign,
..._cannedMgmt,
..._chatlist,
..._contact,
..._contactFilters,
..._conversation,
..._csatMgmtMgmt,
..._generalSettings,

View file

@ -4,8 +4,8 @@
"TITLE": "إعدادات الملف الشخصي",
"BTN_TEXT": "تعديل الملف الشخصي",
"DELETE_AVATAR": "حذف الصورة الرمزية",
"AVATAR_DELETE_SUCCESS": "Avatar has been deleted successfully",
"AVATAR_DELETE_FAILED": "There is an error while deleting avatar, please try again",
"AVATAR_DELETE_SUCCESS": "تم حذف الصورة الرمزية بنجاح",
"AVATAR_DELETE_FAILED": "حدث خطأ أثناء حذف الصورة الرمزية، الرجاء المحاولة مرة أخرى",
"UPDATE_SUCCESS": "تم تحديث حسابك بنجاح",
"PASSWORD_UPDATE_SUCCESS": "تم تغيير كلمة المرور بنجاح",
"AFTER_EMAIL_CHANGED": "تم تحديث ملفك الشخصي بنجاح، الرجاء تسجيل الدخول مرة أخرى حيث أنه قد تم تغيير بيانات تسجيل الدخول الخاصة بك",
@ -89,7 +89,7 @@
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
},
"PASSWORD": {
"LABEL": "كلمة المرور",
"LABEL": "كلمة مرور جديدة",
"ERROR": "الرجاء إدخال كلمة مرور بطول 6 أحرف أو أكثر",
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
},
@ -103,7 +103,7 @@
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "يتغيرون",
"CHANGE_ACCOUNTS": "تبديل الحساب",
"CONTACT_SUPPORT": "Contact Support",
"CONTACT_SUPPORT": "تواصل مع الدعم",
"SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية",
"PROFILE_SETTINGS": "إعدادات الملف الشخصي",
"KEYBOARD_SHORTCUTS": "اختصارات لوحة المفاتيح",
@ -136,7 +136,7 @@
"SIDEBAR": {
"CONVERSATIONS": "المحادثات",
"ALL_CONVERSATIONS": "كل المحادثات",
"MENTIONED_CONVERSATIONS": "Mentions",
"MENTIONED_CONVERSATIONS": "الإشارات",
"REPORTS": "التقارير",
"SETTINGS": "الإعدادات",
"CONTACTS": "جهات الاتصال",
@ -153,11 +153,13 @@
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
"AUTOMATION": "الأتمتة",
"TEAMS": "الفرق",
"CUSTOM_VIEWS_FOLDER": "المجلدات",
"CUSTOM_VIEWS_SEGMENTS": "الأجزاء",
"ALL_CONTACTS": "جميع جهات الاتصال",
"TAGGED_WITH": "مشار إليه بواسطة",
"NEW_LABEL": "New label",
"NEW_TEAM": "New team",
"NEW_INBOX": "New inbox",
"NEW_LABEL": "علامة جديدة",
"NEW_TEAM": "فريق جديد",
"NEW_INBOX": "صندوق الوارد الجديد",
"REPORTS_OVERVIEW": "نظرة عامة",
"CSAT": "CSAT",
"CAMPAIGNS": "الحملات",
@ -167,7 +169,7 @@
"REPORTS_LABEL": "الوسوم",
"REPORTS_INBOX": "صندوق الوارد",
"REPORTS_TEAM": "الفريق",
"SET_AVAILABILITY_TITLE": "Set yourself as"
"SET_AVAILABILITY_TITLE": "تعيين نفسك كـ"
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "أوه! لم نتمكن من العثور على الحساب. الرجاء إنشاء حساب جديد للمتابعة.",

View file

@ -19,7 +19,9 @@
"contains": "Съдържа",
"does_not_contain": "Не съдържа",
"is_present": "Присъства",
"is_not_present": "Не присъства"
"is_not_present": "Не присъства",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"STATUS": "Статус",
@ -31,7 +33,54 @@
"LABELS": "Етикети",
"BROWSER_LANGUAGE": "Език на браузъра",
"COUNTRY_NAME": "Име на държавата",
"REFERER_LINK": "Референтна връзка"
"REFERER_LINK": "Референтна връзка",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Отмени",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
}
},
"DELETE": {
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Потвърди изтриването",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Да, изтрий",
"NO": "No, Keep it"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
}
}
}
}
}

View file

@ -1,6 +1,89 @@
{
"AUTOMATION": {
"HEADER": "Автоматизация",
"HEADER_BTN_TXT": "Добавяне правило за автоматизация"
"HEADER_BTN_TXT": "Добавяне правило за автоматизация",
"LOADING": "Fetching automation rules",
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
"ADD": {
"TITLE": "Добавяне правило за автоматизация",
"SUBMIT": "Създаване",
"CANCEL_BUTTON_TEXT": "Отмени",
"FORM": {
"NAME": {
"LABEL": "Rule Name",
"PLACEHOLDER": "Enter rule name",
"ERROR": "Name is required"
},
"DESC": {
"LABEL": "Описание",
"PLACEHOLDER": "Enter rule description",
"ERROR": "Description is required"
},
"EVENT": {
"LABEL": "Event",
"PLACEHOLDER": "Please select one",
"ERROR": "Event is required"
},
"CONDITIONS": {
"LABEL": "Conditions"
},
"ACTIONS": {
"LABEL": "Действия"
}
},
"CONDITION_BUTTON_LABEL": "Add Condition",
"ACTION_BUTTON_LABEL": "Add Action",
"API": {
"SUCCESS_MESSAGE": "Automation rule added successfully",
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
}
},
"LIST": {
"TABLE_HEADER": [
"Име",
"Описание",
"Активен",
"Created on"
],
"404": "No automation rules found"
},
"DELETE": {
"TITLE": "Delete Automation Rule",
"SUBMIT": "Изтрий",
"CANCEL_BUTTON_TEXT": "Отмени",
"CONFIRM": {
"TITLE": "Потвърди изтриването",
"MESSAGE": "Сигурни ли сте за изтриването ",
"YES": "Да, изтрий ",
"NO": "Не, запази "
},
"API": {
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
}
},
"EDIT": {
"TITLE": "Edit Automation Rule",
"SUBMIT": "Редактирай",
"CANCEL_BUTTON_TEXT": "Отмени",
"API": {
"SUCCESS_MESSAGE": "Automation rule updated successfully",
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
}
},
"CLONE": {
"TOOLTIP": "Clone",
"API": {
"SUCCESS_MESSAGE": "Automation cloned successfully",
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
}
},
"FORM": {
"EDIT": "Редактирай",
"CREATE": "Създаване",
"DELETE": "Изтрий",
"CANCEL": "Отмени",
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
}
}
}

View file

@ -4,7 +4,7 @@
"HEADER_BTN_TXT": "Добавяне на готов отговор",
"LOADING": "Извличане на готови отговори",
"SEARCH_404": "Няма резултати отговарящи на тази заявка",
"SIDEBAR_TXT": "<p><b>Готови отговори</b> </p><p> Готовите отговори са запазени шаблони за отговори, които могат да се използват за бързо изпращане на отговор в разговора. </p><p> За да създадете готов отговор, просто щракнете върху <b>Добавяне на готов отговор</b>. Можете също да редактирате или изтриете съществуващ готов отговор, като щракнете върху бутона Редактиране или Изтриване </p><p> Готовите отговори се използват с помощта на <b>Кратки кодове</b>. Агентите имат достъп до готовите отговори, докато са в чат, като напишат <b>'/'</b>, последвано от краткия код. </p>",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
"LIST": {
"404": "Няма налични готови отговори в този акаунт.",
"TITLE": "Управлявайте готовите отговори",
@ -17,12 +17,12 @@
},
"ADD": {
"TITLE": "Добавяне на готов отговор",
"DESC": "Готовите отговори са предварително дефинирани шаблони за отговор, които могат да се изпращат бързо в чата.",
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
"CANCEL_BUTTON_TEXT": "Отмени",
"FORM": {
"SHORT_CODE": {
"LABEL": "Кратък код",
"PLACEHOLDER": "Моля, въведете кратък код",
"PLACEHOLDER": "Please enter a short code",
"ERROR": "Краткия код е задължителен"
},
"CONTENT": {

View file

@ -169,6 +169,7 @@
"SUBMIT": "Изпрати съобщение",
"CANCEL": "Отмени",
"SUCCESS_MESSAGE": "Съобщението е изпратено!",
"GO_TO_CONVERSATION": "View",
"ERROR_MESSAGE": "Не може да се изпрати! Опитайте пак"
}
},
@ -178,6 +179,8 @@
"SEARCH_BUTTON": "Търсене",
"SEARCH_INPUT_PLACEHOLDER": "Търсене на контакти",
"FILTER_CONTACTS": "Филтър",
"FILTER_CONTACTS_SAVE": "Save filter",
"FILTER_CONTACTS_DELETE": "Delete filter",
"LIST": {
"LOADING_MESSAGE": "Зареждане на контактите...",
"404": "Няма контакти отговарящи на търсенети ви 🔍",

View file

@ -20,7 +20,9 @@
"contains": "Съдържа",
"does_not_contain": "Не съдържа",
"is_present": "Присъства",
"is_not_present": "Не присъства"
"is_not_present": "Не присъства",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"NAME": "Име",
@ -28,7 +30,17 @@
"PHONE_NUMBER": "Телефон",
"IDENTIFIER": "Идентификатор",
"CITY": "Град",
"COUNTRY": "Държава"
"COUNTRY": "Държава",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути"
}
}
}

View file

@ -22,6 +22,8 @@
"LOADING_CONVERSATIONS": "Loading Conversations",
"CANNOT_REPLY": "You cannot reply due to",
"24_HOURS_WINDOW": "24 hour message window restriction",
"NOT_ASSIGNED_TO_YOU": "This conversation is not assigned to you. Would you like to assign this conversation to yourself?",
"ASSIGN_TO_ME": "Assign to me",
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
@ -90,6 +92,9 @@
"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:",
"BOT": "Бот",
"SEND_FAILED": "Couldn't send message! Try again",
"TRY_AGAIN": "retry",
"ASSIGNMENT": {
"SELECT_AGENT": "Select Agent",
"REMOVE": "Remove",
@ -127,7 +132,7 @@
},
"TEAM_MEMBERS": {
"TITLE": "Invite your team members",
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email address to the agent list.",
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email addresses to the agent list.",
"NEW_LINK": "Click here to invite a team member"
},
"INBOXES": {

View file

@ -82,7 +82,7 @@
},
"CHANNEL_GREETING_TOGGLE": {
"LABEL": "Enable channel greeting",
"HELP_TEXT": "Send a greeting message to the user when he starts the conversation.",
"HELP_TEXT": "Send a greeting message to the users when they starts the conversation.",
"ENABLED": "Enabled",
"DISABLED": "Disabled"
},
@ -136,8 +136,56 @@
}
},
"SMS": {
"TITLE": "SMS Channel via Twilio",
"DESC": "Start supporting your customers via SMS with Twilio integration."
"TITLE": "SMS Channel",
"DESC": "Start supporting your customers via SMS.",
"PROVIDERS": {
"LABEL": "API Provider",
"TWILIO": "Twilio",
"BANDWIDTH": "Bandwidth"
},
"API": {
"ERROR_MESSAGE": "We were not able to save the SMS channel"
},
"BANDWIDTH": {
"ACCOUNT_ID": {
"LABEL": "Account ID",
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
"ERROR": "This field is required"
},
"API_KEY": {
"LABEL": "API Key",
"PLACEHOLDER": "Please enter your Bandwith API Key",
"ERROR": "This field is required"
},
"API_SECRET": {
"LABEL": "API Secret",
"PLACEHOLDER": "Please enter your Bandwith API Secret",
"ERROR": "This field is required"
},
"APPLICATION_ID": {
"LABEL": "Application ID",
"PLACEHOLDER": "Please enter your Bandwidth Application ID",
"ERROR": "This field is required"
},
"INBOX_NAME": {
"LABEL": "Име на входящата кутия",
"PLACEHOLDER": "Please enter a inbox name",
"ERROR": "This field is required"
},
"PHONE_NUMBER": {
"LABEL": "Телефон",
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
},
"SUBMIT_BUTTON": "Create Bandwidth Channel",
"API": {
"ERROR_MESSAGE": "We were not able to authenticate Bandwidth credentials, please try again"
},
"API_CALLBACK": {
"TITLE": "Callback URL",
"SUBTITLE": "You have to configure the message callback URL in Bandwidth with the URL mentioned here."
}
}
},
"WHATSAPP": {
"TITLE": "WhatsApp Channel",
@ -305,6 +353,14 @@
"ENABLED": "Enabled",
"DISABLED": "Disabled"
},
"ALLOW_MESSAGES_AFTER_RESOLVED": {
"ENABLED": "Включен",
"DISABLED": "Изключен"
},
"ENABLE_CONTINUITY_VIA_EMAIL": {
"ENABLED": "Включен",
"DISABLED": "Изключен"
},
"ENABLE_HMAC": {
"LABEL": "Enable"
}
@ -351,6 +407,8 @@
"AUTO_ASSIGNMENT": "Enable auto assignment",
"ENABLE_CSAT": "Enable CSAT",
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
"ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email",
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.",
"INBOX_UPDATE_TITLE": "Inbox Settings",
"INBOX_UPDATE_SUB_TEXT": "Update your inbox settings",
"AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.",
@ -361,7 +419,9 @@
"INBOX_IDENTIFIER": "Inbox Identifier",
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
"FORWARD_EMAIL_TITLE": "Forward to Email",
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address."
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address.",
"ALLOW_MESSAGES_AFTER_RESOLVED": "Allow messages after conversation resolved",
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "Allow the end-users to send messages even after the conversation is resolved."
},
"FACEBOOK_REAUTHORIZE": {
"TITLE": "Reauthorize",

View file

@ -0,0 +1,49 @@
import { default as _advancedFilters } from './advancedFilters.json';
import { default as _agentMgmt } from './agentMgmt.json';
import { default as _attributesMgmt } from './attributesMgmt.json';
import { default as _automation } from './automation.json';
import { default as _campaign } from './campaign.json';
import { default as _cannedMgmt } from './cannedMgmt.json';
import { default as _chatlist } from './chatlist.json';
import { default as _contact } from './contact.json';
import { default as _contactFilters } from './contactFilters.json';
import { default as _conversation } from './conversation.json';
import { default as _csatMgmtMgmt } from './csatMgmt.json';
import { default as _generalSettings } from './generalSettings.json';
import { default as _inboxMgmt } from './inboxMgmt.json';
import { default as _integrationApps } from './integrationApps.json';
import { default as _integrations } from './integrations.json';
import { default as _labelsMgmt } from './labelsMgmt.json';
import { default as _login } from './login.json';
import { default as _report } from './report.json';
import { default as _resetPassword } from './resetPassword.json';
import { default as _setNewPassword } from './setNewPassword.json';
import { default as _settings } from './settings.json';
import { default as _signup } from './signup.json';
import { default as _teamsSettings } from './teamsSettings.json';
export default {
..._advancedFilters,
..._agentMgmt,
..._attributesMgmt,
..._automation,
..._campaign,
..._cannedMgmt,
..._chatlist,
..._contact,
..._contactFilters,
..._conversation,
..._csatMgmtMgmt,
..._generalSettings,
..._inboxMgmt,
..._integrationApps,
..._integrations,
..._labelsMgmt,
..._login,
..._report,
..._resetPassword,
..._setNewPassword,
..._settings,
..._signup,
..._teamsSettings,
};

View file

@ -89,14 +89,14 @@
"PLACEHOLDER": "Please enter the current password"
},
"PASSWORD": {
"LABEL": "Password",
"LABEL": "New password",
"ERROR": "Please enter a password of length 6 or more",
"PLACEHOLDER": "Please enter a new password"
},
"PASSWORD_CONFIRMATION": {
"LABEL": "Confirm new password",
"ERROR": "Confirm password should match the password",
"PLACEHOLDER": "Please re-enter your password"
"PLACEHOLDER": "Please re-enter your new password"
}
}
},
@ -153,6 +153,8 @@
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
"AUTOMATION": "Автоматизация",
"TEAMS": "Teams",
"CUSTOM_VIEWS_FOLDER": "Folders",
"CUSTOM_VIEWS_SEGMENTS": "Segments",
"ALL_CONTACTS": "All Contacts",
"TAGGED_WITH": "Tagged with",
"NEW_LABEL": "New label",

View file

@ -2,7 +2,7 @@
"TEAMS_SETTINGS": {
"NEW_TEAM": "Create new team",
"HEADER": "Teams",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> A user can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> An agent can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"LIST": {
"404": "There are no teams created on this account.",
"EDIT_TEAM": "Edit team"

View file

@ -19,7 +19,9 @@
"contains": "Contains",
"does_not_contain": "Does not contain",
"is_present": "Is present",
"is_not_present": "Is not present"
"is_not_present": "Is not present",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"STATUS": "Estat",
@ -31,7 +33,54 @@
"LABELS": "Etiquetes",
"BROWSER_LANGUAGE": "Browser Language",
"COUNTRY_NAME": "Country Name",
"REFERER_LINK": "Referer link"
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Cancel·la",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
}
},
"DELETE": {
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Confirma l'esborrat",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Si, esborra",
"NO": "No, manten-la"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
}
}
}
}
}

View file

@ -90,12 +90,12 @@
}
},
"SEARCH": {
"NO_RESULTS": "No results found."
"NO_RESULTS": "No s'ha trobat agents."
},
"MULTI_SELECTOR": {
"PLACEHOLDER": "None",
"PLACEHOLDER": "Ningú",
"TITLE": {
"AGENT": "Select agent",
"AGENT": "Seleccionar Agent",
"TEAM": "Select team"
},
"SEARCH": {

View file

@ -1,6 +1,89 @@
{
"AUTOMATION": {
"HEADER": "Automation",
"HEADER_BTN_TXT": "Add Automation Rule"
"HEADER_BTN_TXT": "Add Automation Rule",
"LOADING": "Fetching automation rules",
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
"ADD": {
"TITLE": "Add Automation Rule",
"SUBMIT": "Crear",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"NAME": {
"LABEL": "Rule Name",
"PLACEHOLDER": "Enter rule name",
"ERROR": "Name is required"
},
"DESC": {
"LABEL": "Descripció",
"PLACEHOLDER": "Enter rule description",
"ERROR": "Description is required"
},
"EVENT": {
"LABEL": "Event",
"PLACEHOLDER": "Please select one",
"ERROR": "Event is required"
},
"CONDITIONS": {
"LABEL": "Conditions"
},
"ACTIONS": {
"LABEL": "Accions"
}
},
"CONDITION_BUTTON_LABEL": "Add Condition",
"ACTION_BUTTON_LABEL": "Add Action",
"API": {
"SUCCESS_MESSAGE": "Automation rule added successfully",
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
}
},
"LIST": {
"TABLE_HEADER": [
"Nom",
"Descripció",
"Active",
"Created on"
],
"404": "No automation rules found"
},
"DELETE": {
"TITLE": "Delete Automation Rule",
"SUBMIT": "Esborrar",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"CONFIRM": {
"TITLE": "Confirma l'esborrat",
"MESSAGE": "N'estas segur? ",
"YES": "Si, esborra ",
"NO": "No, segueix "
},
"API": {
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
}
},
"EDIT": {
"TITLE": "Edit Automation Rule",
"SUBMIT": "Edita",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"API": {
"SUCCESS_MESSAGE": "Automation rule updated successfully",
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
}
},
"CLONE": {
"TOOLTIP": "Clone",
"API": {
"SUCCESS_MESSAGE": "Automation cloned successfully",
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
}
},
"FORM": {
"EDIT": "Edita",
"CREATE": "Crear",
"DELETE": "Esborrar",
"CANCEL": "Cancel·la",
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
}
}
}

View file

@ -4,7 +4,7 @@
"HEADER_BTN_TXT": "Afegeix una resposta predeterminada",
"LOADING": "S'estan recollint les respostes predeterminades",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta",
"SIDEBAR_TXT": "<p><b>Respostes predeterminades</b> </p><p> Les respostes predeterminades són plantilles de resposta que es poden utilitzar per enviar ràpidament una resposta a una conversa .</p><p> Per crear una Resposta Predeterminada, clica en <b>Afegir Resposta Predeterminada</b>. També pots editar o suprimir una resposta predeterminada fent clic al botó Edita o Suprimeix </p><p> Les respostes predeterminades s'utilitzen amb l'ajuda dels <b>Codi curt</b>. Els agents poden accedir a les respostes predeterminades en un xat escrivint <b>'/'</b> seguit del codi curt. </p>",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
"LIST": {
"404": "No hi ha respostes predeterminades disponibles en aquest compte.",
"TITLE": "Gestiona les respostes predeterminades",
@ -17,12 +17,12 @@
},
"ADD": {
"TITLE": "Afegeix Resposta Predeterminada",
"DESC": "Les respostes predeterminades són plantilles de resposta que es poden utilitzar per enviar ràpidament les respostes a les converses.",
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt",
"PLACEHOLDER": "Please enter a short code",
"ERROR": "És necessari el codi curt"
},
"CONTENT": {

View file

@ -169,6 +169,7 @@
"SUBMIT": "Send message",
"CANCEL": "Cancel·la",
"SUCCESS_MESSAGE": "Message sent!",
"GO_TO_CONVERSATION": "Veure",
"ERROR_MESSAGE": "Couldn't send! try again"
}
},
@ -178,6 +179,8 @@
"SEARCH_BUTTON": "Cercar",
"SEARCH_INPUT_PLACEHOLDER": "Cerca de contactes",
"FILTER_CONTACTS": "Filter",
"FILTER_CONTACTS_SAVE": "Save filter",
"FILTER_CONTACTS_DELETE": "Delete filter",
"LIST": {
"LOADING_MESSAGE": "Carregant contactes...",
"404": "No hi ha cap contacte que coincideixi amb la vostra cerca 🔍",

View file

@ -20,7 +20,9 @@
"contains": "Contains",
"does_not_contain": "Does not contain",
"is_present": "Is present",
"is_not_present": "Is not present"
"is_not_present": "Is not present",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"NAME": "Nom",
@ -28,7 +30,17 @@
"PHONE_NUMBER": "Número de telèfon",
"IDENTIFIER": "Identifier",
"CITY": "City",
"COUNTRY": "Country"
"COUNTRY": "Country",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
}
}
}

View file

@ -22,6 +22,8 @@
"LOADING_CONVERSATIONS": "S'estan carregant les converses",
"CANNOT_REPLY": "No pots respondre degut a",
"24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
"NOT_ASSIGNED_TO_YOU": "This conversation is not assigned to you. Would you like to assign this conversation to yourself?",
"ASSIGN_TO_ME": "Assign to me",
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
@ -90,6 +92,9 @@
"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": "Enviat per:",
"BOT": "Bot",
"SEND_FAILED": "Couldn't send message! Try again",
"TRY_AGAIN": "retry",
"ASSIGNMENT": {
"SELECT_AGENT": "Seleccionar Agent",
"REMOVE": "Suprimeix",
@ -127,7 +132,7 @@
},
"TEAM_MEMBERS": {
"TITLE": "Invite your team members",
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email address to the agent list.",
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email addresses to the agent list.",
"NEW_LINK": "Click here to invite a team member"
},
"INBOXES": {

View file

@ -40,7 +40,7 @@
"AUTO_RESOLVE_DURATION": {
"LABEL": "El nombre de dies després que un ticket es resolgui automàticament si no hi ha activitat",
"PLACEHOLDER": "30",
"ERROR": "Introdueix una durada vàlida de resolució automàtica (mínim 1 dia)"
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
},
"FEATURES": {
"INBOUND_EMAIL_ENABLED": "La continuïtat de converses amb correus electrònics està habilitada per al vostre compte.",

View file

@ -82,7 +82,7 @@
},
"CHANNEL_GREETING_TOGGLE": {
"LABEL": "Activa la salutació del canal",
"HELP_TEXT": "Envia un missatge de felicitació a l'usuari quan comenci la conversa.",
"HELP_TEXT": "Send a greeting message to the users when they starts the conversation.",
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
@ -136,8 +136,56 @@
}
},
"SMS": {
"TITLE": "SMS Channel via Twilio",
"DESC": "Start supporting your customers via SMS with Twilio integration."
"TITLE": "SMS Channel",
"DESC": "Start supporting your customers via SMS.",
"PROVIDERS": {
"LABEL": "API Provider",
"TWILIO": "Twilio",
"BANDWIDTH": "Bandwidth"
},
"API": {
"ERROR_MESSAGE": "We were not able to save the SMS channel"
},
"BANDWIDTH": {
"ACCOUNT_ID": {
"LABEL": "Account ID",
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
"ERROR": "Aquest camp és obligatori"
},
"API_KEY": {
"LABEL": "API Key",
"PLACEHOLDER": "Please enter your Bandwith API Key",
"ERROR": "Aquest camp és obligatori"
},
"API_SECRET": {
"LABEL": "API Secret",
"PLACEHOLDER": "Please enter your Bandwith API Secret",
"ERROR": "Aquest camp és obligatori"
},
"APPLICATION_ID": {
"LABEL": "Application ID",
"PLACEHOLDER": "Please enter your Bandwidth Application ID",
"ERROR": "Aquest camp és obligatori"
},
"INBOX_NAME": {
"LABEL": "Nom de la safata d'entrada",
"PLACEHOLDER": "Please enter a inbox name",
"ERROR": "Aquest camp és obligatori"
},
"PHONE_NUMBER": {
"LABEL": "Número de telèfon",
"PLACEHOLDER": "Introduïu el número de telèfon des del qual serà enviat el missatge.",
"ERROR": "Introduïu un valor vàlid. El número de telèfon hauria de començar amb el signe `+`."
},
"SUBMIT_BUTTON": "Create Bandwidth Channel",
"API": {
"ERROR_MESSAGE": "We were not able to authenticate Bandwidth credentials, please try again"
},
"API_CALLBACK": {
"TITLE": "Callback URL",
"SUBTITLE": "You have to configure the message callback URL in Bandwidth with the URL mentioned here."
}
}
},
"WHATSAPP": {
"TITLE": "WhatsApp Channel",
@ -305,6 +353,14 @@
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
"ALLOW_MESSAGES_AFTER_RESOLVED": {
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
"ENABLE_CONTINUITY_VIA_EMAIL": {
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
"ENABLE_HMAC": {
"LABEL": "Enable"
}
@ -351,6 +407,8 @@
"AUTO_ASSIGNMENT": "Activa l'assignació automàtica",
"ENABLE_CSAT": "Enable CSAT",
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
"ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email",
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.",
"INBOX_UPDATE_TITLE": "Configuració de la safata d'entrada",
"INBOX_UPDATE_SUB_TEXT": "Actualitza la configuració de la safata d'entrada",
"AUTO_ASSIGNMENT_SUB_TEXT": "Activa o desactiva l'assignació automàtica d'agents disponibles a les noves converses",
@ -361,7 +419,9 @@
"INBOX_IDENTIFIER": "Inbox Identifier",
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
"FORWARD_EMAIL_TITLE": "Forward to Email",
"FORWARD_EMAIL_SUB_TEXT": "Comença a reenviar els teus correus electrònics a la següent adreça electrònica."
"FORWARD_EMAIL_SUB_TEXT": "Comença a reenviar els teus correus electrònics a la següent adreça electrònica.",
"ALLOW_MESSAGES_AFTER_RESOLVED": "Allow messages after conversation resolved",
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "Allow the end-users to send messages even after the conversation is resolved."
},
"FACEBOOK_REAUTHORIZE": {
"TITLE": "Reautoritza",

View file

@ -1,9 +1,12 @@
import { default as _advancedFilters } from './advancedFilters.json';
import { default as _agentMgmt } from './agentMgmt.json';
import { default as _attributesMgmt } from './attributesMgmt.json';
import { default as _automation } from './automation.json';
import { default as _campaign } from './campaign.json';
import { default as _cannedMgmt } from './cannedMgmt.json';
import { default as _chatlist } from './chatlist.json';
import { default as _contact } from './contact.json';
import { default as _contactFilters } from './contactFilters.json';
import { default as _conversation } from './conversation.json';
import { default as _csatMgmtMgmt } from './csatMgmt.json';
import { default as _generalSettings } from './generalSettings.json';
@ -20,12 +23,15 @@ import { default as _signup } from './signup.json';
import { default as _teamsSettings } from './teamsSettings.json';
export default {
..._advancedFilters,
..._agentMgmt,
..._attributesMgmt,
..._automation,
..._campaign,
..._cannedMgmt,
..._chatlist,
..._contact,
..._contactFilters,
..._conversation,
..._csatMgmtMgmt,
..._generalSettings,

View file

@ -89,14 +89,14 @@
"PLACEHOLDER": "Please enter the current password"
},
"PASSWORD": {
"LABEL": "Contrasenya",
"LABEL": "New password",
"ERROR": "Introduïu una contrasenya d'una longitud de 6 o més",
"PLACEHOLDER": "Introduïu una nova contrasenya"
},
"PASSWORD_CONFIRMATION": {
"LABEL": "Confirmació de la nova contrasenya",
"ERROR": "Confirmeu que les contrasenyes coincideixin",
"PLACEHOLDER": "Torneu a introduir la vostra contrasenya"
"PLACEHOLDER": "Please re-enter your new password"
}
}
},
@ -153,6 +153,8 @@
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
"AUTOMATION": "Automation",
"TEAMS": "Equips",
"CUSTOM_VIEWS_FOLDER": "Folders",
"CUSTOM_VIEWS_SEGMENTS": "Segments",
"ALL_CONTACTS": "All Contacts",
"TAGGED_WITH": "Tagged with",
"NEW_LABEL": "New label",

View file

@ -2,7 +2,7 @@
"TEAMS_SETTINGS": {
"NEW_TEAM": "Create new team",
"HEADER": "Equips",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> A user can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> An agent can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"LIST": {
"404": "There are no teams created on this account.",
"EDIT_TEAM": "Edit team"

View file

@ -19,7 +19,9 @@
"contains": "Contains",
"does_not_contain": "Does not contain",
"is_present": "Is present",
"is_not_present": "Is not present"
"is_not_present": "Is not present",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"STATUS": "Stav",
@ -31,7 +33,54 @@
"LABELS": "Štítky",
"BROWSER_LANGUAGE": "Browser Language",
"COUNTRY_NAME": "Country Name",
"REFERER_LINK": "Referer link"
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Vlastní atributy"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Zrušit",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
}
},
"DELETE": {
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Potvrdit odstranění",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Ano, odstranit",
"NO": "No, Keep it"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
}
}
}
}
}

View file

@ -1,6 +1,89 @@
{
"AUTOMATION": {
"HEADER": "Automation",
"HEADER_BTN_TXT": "Add Automation Rule"
"HEADER_BTN_TXT": "Add Automation Rule",
"LOADING": "Fetching automation rules",
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
"ADD": {
"TITLE": "Add Automation Rule",
"SUBMIT": "Create",
"CANCEL_BUTTON_TEXT": "Zrušit",
"FORM": {
"NAME": {
"LABEL": "Rule Name",
"PLACEHOLDER": "Enter rule name",
"ERROR": "Name is required"
},
"DESC": {
"LABEL": "Description",
"PLACEHOLDER": "Enter rule description",
"ERROR": "Description is required"
},
"EVENT": {
"LABEL": "Event",
"PLACEHOLDER": "Please select one",
"ERROR": "Event is required"
},
"CONDITIONS": {
"LABEL": "Conditions"
},
"ACTIONS": {
"LABEL": "Akce"
}
},
"CONDITION_BUTTON_LABEL": "Add Condition",
"ACTION_BUTTON_LABEL": "Add Action",
"API": {
"SUCCESS_MESSAGE": "Automation rule added successfully",
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
}
},
"LIST": {
"TABLE_HEADER": [
"Název",
"Description",
"Active",
"Created on"
],
"404": "No automation rules found"
},
"DELETE": {
"TITLE": "Delete Automation Rule",
"SUBMIT": "Vymazat",
"CANCEL_BUTTON_TEXT": "Zrušit",
"CONFIRM": {
"TITLE": "Potvrdit odstranění",
"MESSAGE": "Opravdu chcete odstranit ",
"YES": "Ano, odstranit ",
"NO": "Ne, zachovat "
},
"API": {
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
}
},
"EDIT": {
"TITLE": "Edit Automation Rule",
"SUBMIT": "Upravit",
"CANCEL_BUTTON_TEXT": "Zrušit",
"API": {
"SUCCESS_MESSAGE": "Automation rule updated successfully",
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
}
},
"CLONE": {
"TOOLTIP": "Clone",
"API": {
"SUCCESS_MESSAGE": "Automation cloned successfully",
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
}
},
"FORM": {
"EDIT": "Upravit",
"CREATE": "Create",
"DELETE": "Vymazat",
"CANCEL": "Zrušit",
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
}
}
}

View file

@ -4,7 +4,7 @@
"HEADER_BTN_TXT": "Přidat konzervovanou odpověď",
"LOADING": "Načítání Konzervovaných odpovědí",
"SEARCH_404": "Neexistují žádné položky odpovídající tomuto dotazu",
"SIDEBAR_TXT": "<p><b>Canned Response</b> </p><p> Canned Response are saved šablony odpovědí, které mohou být použity pro rychlé odeslání odpovědi do konverzace . </p><p> Pro vytvoření Canned Response klikněte na <b>Přidat Canned Response</b>. Můžete také upravit nebo odstranit existující Canned Response kliknutím na tlačítko Upravit nebo vymazat </p><p> Rušené odpovědi jsou použity s pomocí <b>Krátké kódy</b>. Pracovníci mohou během chatu získat přístup k uloženým odpovědím napsaním <b>'/'</b> následovaným krátkým kódem. </p>",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
"LIST": {
"404": "V tomto účtu nejsou k dispozici žádné konzervované odpovědi.",
"TITLE": "Spravovat konzervované odpovědi",
@ -17,12 +17,12 @@
},
"ADD": {
"TITLE": "Přidat konzervovanou odpověď",
"DESC": "Konzervované odpovědi jsou uložené šablony odpovědí, které lze použít pro rychlé odeslání odpovědi do konverzace .",
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
"CANCEL_BUTTON_TEXT": "Zrušit",
"FORM": {
"SHORT_CODE": {
"LABEL": "Krátký kód",
"PLACEHOLDER": "Zadejte zkratkový kód",
"PLACEHOLDER": "Please enter a short code",
"ERROR": "Krátký kód je povinný"
},
"CONTENT": {

View file

@ -169,6 +169,7 @@
"SUBMIT": "Send message",
"CANCEL": "Zrušit",
"SUCCESS_MESSAGE": "Message sent!",
"GO_TO_CONVERSATION": "Zobrazit",
"ERROR_MESSAGE": "Couldn't send! try again"
}
},
@ -178,6 +179,8 @@
"SEARCH_BUTTON": "Hledat",
"SEARCH_INPUT_PLACEHOLDER": "Hledat kontakty",
"FILTER_CONTACTS": "Filter",
"FILTER_CONTACTS_SAVE": "Save filter",
"FILTER_CONTACTS_DELETE": "Delete filter",
"LIST": {
"LOADING_MESSAGE": "Načítání kontaktů...",
"404": "Vašemu hledání neodpovídají žádné kontakty 🔍",

View file

@ -20,7 +20,9 @@
"contains": "Contains",
"does_not_contain": "Does not contain",
"is_present": "Is present",
"is_not_present": "Is not present"
"is_not_present": "Is not present",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"NAME": "Název",
@ -28,7 +30,17 @@
"PHONE_NUMBER": "Telefonní číslo",
"IDENTIFIER": "Identifier",
"CITY": "Město",
"COUNTRY": "Země"
"COUNTRY": "Země",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Vlastní atributy"
}
}
}

View file

@ -22,6 +22,8 @@
"LOADING_CONVERSATIONS": "Načítání konverzací",
"CANNOT_REPLY": "Nemůžete odpovědět z důvodu",
"24_HOURS_WINDOW": "24 hodinové omezení okna",
"NOT_ASSIGNED_TO_YOU": "This conversation is not assigned to you. Would you like to assign this conversation to yourself?",
"ASSIGN_TO_ME": "Přiřadit mi",
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hodinové omezení okna",
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
@ -90,6 +92,9 @@
"FILE_SIZE_LIMIT": "Soubor překračuje limit {MAXIMUM_FILE_UPLOAD_SIZE} přílohy",
"MESSAGE_ERROR": "Unable to send this message, please try again later",
"SENT_BY": "Odeslal:",
"BOT": "Bot",
"SEND_FAILED": "Couldn't send message! Try again",
"TRY_AGAIN": "retry",
"ASSIGNMENT": {
"SELECT_AGENT": "Vybrat agenta",
"REMOVE": "Odebrat",
@ -127,7 +132,7 @@
},
"TEAM_MEMBERS": {
"TITLE": "Pozvěte své členy týmu",
"DESCRIPTION": "Vzhledem k tomu, že se připravujete na rozhovory se zákazníkem, vdechněte své týmové spolupracovníky, kteří vám pomohou. Můžete pozvat své přátele přidáním jejich e-mailové adresy do seznamu agentů.",
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email addresses to the agent list.",
"NEW_LINK": "Klikněte zde pro pozvání člena týmu"
},
"INBOXES": {

View file

@ -40,7 +40,7 @@
"AUTO_RESOLVE_DURATION": {
"LABEL": "Počet dnů, po kterých by měl být ticket automaticky vyřešen při žádné aktivitě",
"PLACEHOLDER": "30",
"ERROR": "Zadejte platnou hodnotu automatického vyřešení (minimálně 1 den)"
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
},
"FEATURES": {
"INBOUND_EMAIL_ENABLED": "E-mailová konverzace je u vašeho účtu povolena.",

View file

@ -82,7 +82,7 @@
},
"CHANNEL_GREETING_TOGGLE": {
"LABEL": "Povolit uvítání",
"HELP_TEXT": "Poslat uvítací zprávu uživateli, když začne konverzaci.",
"HELP_TEXT": "Send a greeting message to the users when they starts the conversation.",
"ENABLED": "Povoleno",
"DISABLED": "Zakázáno"
},
@ -136,8 +136,56 @@
}
},
"SMS": {
"TITLE": "SMS Channel via Twilio",
"DESC": "Start supporting your customers via SMS with Twilio integration."
"TITLE": "SMS Channel",
"DESC": "Start supporting your customers via SMS.",
"PROVIDERS": {
"LABEL": "API Provider",
"TWILIO": "Twilio",
"BANDWIDTH": "Bandwidth"
},
"API": {
"ERROR_MESSAGE": "We were not able to save the SMS channel"
},
"BANDWIDTH": {
"ACCOUNT_ID": {
"LABEL": "Account ID",
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
"ERROR": "Toto pole je povinné"
},
"API_KEY": {
"LABEL": "API Key",
"PLACEHOLDER": "Please enter your Bandwith API Key",
"ERROR": "Toto pole je povinné"
},
"API_SECRET": {
"LABEL": "API Secret",
"PLACEHOLDER": "Please enter your Bandwith API Secret",
"ERROR": "Toto pole je povinné"
},
"APPLICATION_ID": {
"LABEL": "Application ID",
"PLACEHOLDER": "Please enter your Bandwidth Application ID",
"ERROR": "Toto pole je povinné"
},
"INBOX_NAME": {
"LABEL": "Název schránky",
"PLACEHOLDER": "Please enter a inbox name",
"ERROR": "Toto pole je povinné"
},
"PHONE_NUMBER": {
"LABEL": "Telefonní číslo",
"PLACEHOLDER": "Zadejte prosím telefonní číslo, ze kterého bude zpráva odeslána.",
"ERROR": "Zadejte platnou hodnotu. Telefonní číslo by mělo začínat znakem `+`."
},
"SUBMIT_BUTTON": "Create Bandwidth Channel",
"API": {
"ERROR_MESSAGE": "We were not able to authenticate Bandwidth credentials, please try again"
},
"API_CALLBACK": {
"TITLE": "Callback URL",
"SUBTITLE": "You have to configure the message callback URL in Bandwidth with the URL mentioned here."
}
}
},
"WHATSAPP": {
"TITLE": "WhatsApp Channel",
@ -305,6 +353,14 @@
"ENABLED": "Povoleno",
"DISABLED": "Zakázáno"
},
"ALLOW_MESSAGES_AFTER_RESOLVED": {
"ENABLED": "Povoleno",
"DISABLED": "Zakázáno"
},
"ENABLE_CONTINUITY_VIA_EMAIL": {
"ENABLED": "Povoleno",
"DISABLED": "Zakázáno"
},
"ENABLE_HMAC": {
"LABEL": "Enable"
}
@ -351,6 +407,8 @@
"AUTO_ASSIGNMENT": "Povolit automatické přiřazení",
"ENABLE_CSAT": "Enable CSAT",
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
"ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email",
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.",
"INBOX_UPDATE_TITLE": "Nastavení doručené pošty",
"INBOX_UPDATE_SUB_TEXT": "Aktualizujte nastavení doručené pošty",
"AUTO_ASSIGNMENT_SUB_TEXT": "Povolit nebo zakázat automatické přiřazování nových konverzací agentům přidaným do této schránky.",
@ -361,7 +419,9 @@
"INBOX_IDENTIFIER": "Inbox Identifier",
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
"FORWARD_EMAIL_TITLE": "Forward to Email",
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address."
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address.",
"ALLOW_MESSAGES_AFTER_RESOLVED": "Allow messages after conversation resolved",
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "Allow the end-users to send messages even after the conversation is resolved."
},
"FACEBOOK_REAUTHORIZE": {
"TITLE": "Znovu autorizovat",

View file

@ -1,9 +1,12 @@
import { default as _advancedFilters } from './advancedFilters.json';
import { default as _agentMgmt } from './agentMgmt.json';
import { default as _attributesMgmt } from './attributesMgmt.json';
import { default as _automation } from './automation.json';
import { default as _campaign } from './campaign.json';
import { default as _cannedMgmt } from './cannedMgmt.json';
import { default as _chatlist } from './chatlist.json';
import { default as _contact } from './contact.json';
import { default as _contactFilters } from './contactFilters.json';
import { default as _conversation } from './conversation.json';
import { default as _csatMgmtMgmt } from './csatMgmt.json';
import { default as _generalSettings } from './generalSettings.json';
@ -20,12 +23,15 @@ import { default as _signup } from './signup.json';
import { default as _teamsSettings } from './teamsSettings.json';
export default {
..._advancedFilters,
..._agentMgmt,
..._attributesMgmt,
..._automation,
..._campaign,
..._cannedMgmt,
..._chatlist,
..._contact,
..._contactFilters,
..._conversation,
..._csatMgmtMgmt,
..._generalSettings,

View file

@ -89,14 +89,14 @@
"PLACEHOLDER": "Please enter the current password"
},
"PASSWORD": {
"LABEL": "Heslo",
"LABEL": "New password",
"ERROR": "Zadejte prosím heslo o délce 6 nebo více",
"PLACEHOLDER": "Zadejte prosím nové heslo"
},
"PASSWORD_CONFIRMATION": {
"LABEL": "Potvrdit nové heslo",
"ERROR": "Potvrzení hesla by mělo odpovídat heslu",
"PLACEHOLDER": "Zadejte prosím znovu své heslo"
"PLACEHOLDER": "Please re-enter your new password"
}
}
},
@ -153,6 +153,8 @@
"CUSTOM_ATTRIBUTES": "Vlastní atributy",
"AUTOMATION": "Automation",
"TEAMS": "Týmy",
"CUSTOM_VIEWS_FOLDER": "Folders",
"CUSTOM_VIEWS_SEGMENTS": "Segments",
"ALL_CONTACTS": "All Contacts",
"TAGGED_WITH": "Tagged with",
"NEW_LABEL": "New label",

View file

@ -2,7 +2,7 @@
"TEAMS_SETTINGS": {
"NEW_TEAM": "Create new team",
"HEADER": "Týmy",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> A user can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> An agent can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"LIST": {
"404": "There are no teams created on this account.",
"EDIT_TEAM": "Edit team"

View file

@ -19,7 +19,9 @@
"contains": "Contains",
"does_not_contain": "Does not contain",
"is_present": "Is present",
"is_not_present": "Is not present"
"is_not_present": "Is not present",
"is_greater_than": "Is greater than",
"is_lesser_than": "Is lesser than"
},
"ATTRIBUTES": {
"STATUS": "Status",
@ -31,7 +33,54 @@
"LABELS": "Etiketter",
"BROWSER_LANGUAGE": "Browser Language",
"COUNTRY_NAME": "Country Name",
"REFERER_LINK": "Referer link"
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Text",
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
"CUSTOM_ATTRIBUTE_LINK": "Link",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
"ADDITIONAL_FILTERS": "Additional Filters",
"CUSTOM_ATTRIBUTES": "Brugerdefinerede Egenskaber"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Enter a name for this filter",
"ERROR_MESSAGE": "Name is required",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Annuller",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully",
"ERROR_MESSAGE": "Error while creating folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully",
"ERROR_MESSAGE": "Error while creating segment"
}
},
"DELETE": {
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Bekræft Sletning",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Ja, Slet",
"NO": "Nej, behold det"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully",
"ERROR_MESSAGE": "Error while deleting folder"
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully",
"ERROR_MESSAGE": "Error while deleting segment"
}
}
}
}
}

View file

@ -1,6 +1,89 @@
{
"AUTOMATION": {
"HEADER": "Automation",
"HEADER_BTN_TXT": "Add Automation Rule"
"HEADER_BTN_TXT": "Add Automation Rule",
"LOADING": "Fetching automation rules",
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
"ADD": {
"TITLE": "Add Automation Rule",
"SUBMIT": "Opret",
"CANCEL_BUTTON_TEXT": "Annuller",
"FORM": {
"NAME": {
"LABEL": "Rule Name",
"PLACEHOLDER": "Enter rule name",
"ERROR": "Name is required"
},
"DESC": {
"LABEL": "Beskrivelse",
"PLACEHOLDER": "Enter rule description",
"ERROR": "Description is required"
},
"EVENT": {
"LABEL": "Event",
"PLACEHOLDER": "Please select one",
"ERROR": "Event is required"
},
"CONDITIONS": {
"LABEL": "Conditions"
},
"ACTIONS": {
"LABEL": "Handlinger"
}
},
"CONDITION_BUTTON_LABEL": "Add Condition",
"ACTION_BUTTON_LABEL": "Add Action",
"API": {
"SUCCESS_MESSAGE": "Automation rule added successfully",
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
}
},
"LIST": {
"TABLE_HEADER": [
"Navn",
"Beskrivelse",
"Active",
"Created on"
],
"404": "No automation rules found"
},
"DELETE": {
"TITLE": "Delete Automation Rule",
"SUBMIT": "Slet",
"CANCEL_BUTTON_TEXT": "Annuller",
"CONFIRM": {
"TITLE": "Bekræft Sletning",
"MESSAGE": "Er du sikker på du vil slette ",
"YES": "Ja, Slet ",
"NO": "Nej, Behold "
},
"API": {
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
}
},
"EDIT": {
"TITLE": "Edit Automation Rule",
"SUBMIT": "Rediger",
"CANCEL_BUTTON_TEXT": "Annuller",
"API": {
"SUCCESS_MESSAGE": "Automation rule updated successfully",
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
}
},
"CLONE": {
"TOOLTIP": "Clone",
"API": {
"SUCCESS_MESSAGE": "Automation cloned successfully",
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
}
},
"FORM": {
"EDIT": "Rediger",
"CREATE": "Opret",
"DELETE": "Slet",
"CANCEL": "Annuller",
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
}
}
}

Some files were not shown because too many files have changed in this diff Show more