Merge branch 'release/2.11.0'
This commit is contained in:
commit
8a0d6f6f50
740 changed files with 17226 additions and 2665 deletions
|
@ -54,3 +54,5 @@ exclude_patterns:
|
||||||
- 'app/javascript/widget/i18n/index.js'
|
- 'app/javascript/widget/i18n/index.js'
|
||||||
- 'app/javascript/survey/i18n/index.js'
|
- 'app/javascript/survey/i18n/index.js'
|
||||||
- 'app/javascript/shared/constants/locales.js'
|
- 'app/javascript/shared/constants/locales.js'
|
||||||
|
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
|
||||||
|
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'
|
||||||
|
|
|
@ -34,6 +34,11 @@ REDIS_SENTINELS=
|
||||||
# You can find list of master using "SENTINEL masters" command
|
# You can find list of master using "SENTINEL masters" command
|
||||||
REDIS_SENTINEL_MASTER_NAME=
|
REDIS_SENTINEL_MASTER_NAME=
|
||||||
|
|
||||||
|
# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels
|
||||||
|
# Use the following environment variable to customize passwords for sentinels.
|
||||||
|
# Use empty string if sentinels are configured with out passwords
|
||||||
|
# REDIS_SENTINEL_PASSWORD=
|
||||||
|
|
||||||
# Redis premium breakage in heroku fix
|
# Redis premium breakage in heroku fix
|
||||||
# enable the following configuration
|
# enable the following configuration
|
||||||
# ref: https://github.com/chatwoot/chatwoot/issues/2420
|
# ref: https://github.com/chatwoot/chatwoot/issues/2420
|
||||||
|
@ -51,7 +56,7 @@ RAILS_MAX_THREADS=5
|
||||||
|
|
||||||
# The email from which all outgoing emails are sent
|
# The email from which all outgoing emails are sent
|
||||||
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
||||||
MAILER_SENDER_EMAIL="Chatwoot <accounts@chatwoot.com>"
|
MAILER_SENDER_EMAIL=Chatwoot <accounts@chatwoot.com>
|
||||||
|
|
||||||
#SMTP domain key is set up for HELO checking
|
#SMTP domain key is set up for HELO checking
|
||||||
SMTP_DOMAIN=chatwoot.com
|
SMTP_DOMAIN=chatwoot.com
|
||||||
|
|
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -6,6 +6,7 @@ labels: 'Bug'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
@ -16,11 +17,11 @@ Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See the error
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
|
|
||||||
A clear and concise description of what you expected to happen.
|
Share a clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
|
|
||||||
|
@ -28,27 +29,50 @@ If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Browser logs**
|
**Browser logs**
|
||||||
|
|
||||||
Share the browser logs to debug the issue further
|
Share the browser logs to debug the issue further.
|
||||||
|
|
||||||
**Server logs**
|
**Server logs**
|
||||||
|
|
||||||
Share the server logs to debug the issue further
|
Share the server logs to debug the issue further.
|
||||||
|
|
||||||
**Environment**
|
**Environment**
|
||||||
|
|
||||||
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku)
|
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self-hosted installation of Chatwoot. If you are using a self-hosted installation of Chatwoot, describe the type of deployment (Docker/Linux VM installation/Heroku/Kubernetes/Other).
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
- [ ] app.chatwoot.com (Chatwoot Cloud)
|
||||||
- OS: [e.g. iOS]
|
- [ ] Self-hosted
|
||||||
- Browser [e.g. chrome, safari]
|
- - [ ] Linux VM
|
||||||
|
- - [ ] Docker
|
||||||
|
- - [ ] Kubernetes
|
||||||
|
- - [ ] Heroku
|
||||||
|
- - [ ] Other (Please specify)
|
||||||
|
|
||||||
|
|
||||||
|
**Desktop (please complete the following information)** (If applicable)
|
||||||
|
- OS: [e.g. Linux, Windows, MacOS]
|
||||||
|
- Browser [e.g. chrome, firefox, safari]
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 22]
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
**Smartphone (please complete the following information)** (If applicable)
|
||||||
- Device: [e.g. iPhone6]
|
- Device: [e.g. iPhone6, Pixel7]
|
||||||
- OS: [e.g. iOS8.1]
|
- OS: [e.g. iOS8.1]
|
||||||
- Browser [e.g. stock browser, safari]
|
- Browser [e.g. stock browser, firefox, safari]
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Docker** (If applicable)
|
||||||
|
|
||||||
|
Please share the output of the following.
|
||||||
|
- `docker version`
|
||||||
|
- `docker info`
|
||||||
|
- `docker-compose version`
|
||||||
|
|
||||||
|
**Cloud Provider** (If applicable)
|
||||||
|
- [ ] AWS
|
||||||
|
- [ ] GCP
|
||||||
|
- [ ] Azure
|
||||||
|
- [ ] DigitalOcean
|
||||||
|
- [ ] Others
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires.
|
||||||
|
|
||||||
Fixes # (issue)
|
Fixes # (issue)
|
||||||
|
|
||||||
## Type of change
|
## Type of change
|
||||||
|
@ -12,18 +11,18 @@ Please delete options that are not relevant.
|
||||||
|
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected)
|
||||||
- [ ] This change requires a documentation update
|
- [ ] This change requires a documentation update
|
||||||
|
|
||||||
## How Has This Been Tested?
|
## How Has This Been Tested?
|
||||||
|
|
||||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
|
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
|
||||||
|
|
||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
|
|
||||||
- [ ] My code follows the style guidelines of this project
|
- [ ] My code follows the style guidelines of this project
|
||||||
- [ ] I have performed a self-review of my own code
|
- [ ] I have performed a self-review of my code
|
||||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||||
- [ ] I have made corresponding changes to the documentation
|
- [ ] I have made corresponding changes to the documentation
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -60,3 +60,5 @@ test/cypress/videos/*
|
||||||
|
|
||||||
/config/master.key
|
/config/master.key
|
||||||
/config/*.enc
|
/config/*.enc
|
||||||
|
|
||||||
|
.vscode/settings.json
|
||||||
|
|
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -427,14 +427,14 @@ GEM
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
newrelic_rpm (8.9.0)
|
newrelic_rpm (8.9.0)
|
||||||
nio4r (2.5.8)
|
nio4r (2.5.8)
|
||||||
nokogiri (1.13.7)
|
nokogiri (1.13.9)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.7-arm64-darwin)
|
nokogiri (1.13.9-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.7-x86_64-darwin)
|
nokogiri (1.13.9-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.7-x86_64-linux)
|
nokogiri (1.13.9-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oauth (0.5.10)
|
oauth (0.5.10)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
|
@ -808,4 +808,4 @@ RUBY VERSION
|
||||||
ruby 3.0.4p208
|
ruby 3.0.4p208
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.18
|
2.3.16
|
||||||
|
|
|
@ -72,6 +72,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||||
|
|
||||||
def build_message
|
def build_message
|
||||||
return if @outgoing_echo && already_sent_from_chatwoot?
|
return if @outgoing_echo && already_sent_from_chatwoot?
|
||||||
|
return if message_content.blank? && all_unsupported_files?
|
||||||
|
|
||||||
@message = conversation.messages.create!(message_params)
|
@message = conversation.messages.create!(message_params)
|
||||||
|
|
||||||
|
@ -117,6 +118,13 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||||
cw_message.present?
|
cw_message.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def all_unsupported_files?
|
||||||
|
return if attachments.empty?
|
||||||
|
|
||||||
|
attachments_type = attachments.pluck(:type).uniq.first
|
||||||
|
unsupported_file_type?(attachments_type)
|
||||||
|
end
|
||||||
|
|
||||||
### Sample response
|
### Sample response
|
||||||
# {
|
# {
|
||||||
# "object": "instagram",
|
# "object": "instagram",
|
||||||
|
|
|
@ -35,7 +35,13 @@ class Messages::MessageBuilder
|
||||||
file: uploaded_attachment
|
file: uploaded_attachment
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment.file_type = file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
|
attachment.file_type = if uploaded_attachment.is_a?(String)
|
||||||
|
file_type_by_signed_id(
|
||||||
|
uploaded_attachment
|
||||||
|
)
|
||||||
|
else
|
||||||
|
file_type(uploaded_attachment&.content_type)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ class Messages::Messenger::MessageBuilder
|
||||||
include ::FileTypeHelper
|
include ::FileTypeHelper
|
||||||
|
|
||||||
def process_attachment(attachment)
|
def process_attachment(attachment)
|
||||||
return if attachment['type'].to_sym == :template
|
# This check handles very rare case if there are multiple files to attach with only one usupported file
|
||||||
|
return if unsupported_file_type?(attachment['type'])
|
||||||
|
|
||||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||||
attachment_obj.save!
|
attachment_obj.save!
|
||||||
|
@ -80,4 +81,10 @@ class Messages::Messenger::MessageBuilder
|
||||||
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
|
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def unsupported_file_type?(attachment_type)
|
||||||
|
[:template, :unsupported_type].include? attachment_type.to_sym
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,9 +5,10 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||||
before_action :set_current_page, only: [:index]
|
before_action :set_current_page, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@articles_count = @portal.articles.count
|
@portal_articles = @portal.articles
|
||||||
@articles = @portal.articles
|
@all_articles = @portal_articles.search(list_params)
|
||||||
@articles = @articles.search(list_params) if list_params.present?
|
@articles_count = @all_articles.count
|
||||||
|
@articles = @all_articles.page(@current_page)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -37,7 +38,7 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def portal
|
def portal
|
||||||
@portal ||= Current.account.portals.find_by(slug: params[:portal_id])
|
@portal ||= Current.account.portals.find_by!(slug: params[:portal_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def article_params
|
def article_params
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
|
||||||
before_action :set_current_page, only: [:index]
|
before_action :set_current_page, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@current_locale = params[:locale]
|
||||||
@categories = @portal.categories.search(params)
|
@categories = @portal.categories.search(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
before_action :check_authorization
|
|
||||||
before_action :fetch_macro, only: [:show, :update, :destroy, :execute]
|
before_action :fetch_macro, only: [:show, :update, :destroy, :execute]
|
||||||
|
before_action :check_authorization, only: [:show, :update, :destroy, :execute]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@macros = Macro.with_visibility(current_user, params)
|
@macros = Macro.with_visibility(current_user, params)
|
||||||
|
@ -14,6 +14,8 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
render json: { error: @macro.errors.messages }, status: :unprocessable_entity and return unless @macro.valid?
|
render json: { error: @macro.errors.messages }, status: :unprocessable_entity and return unless @macro.valid?
|
||||||
|
|
||||||
@macro.save!
|
@macro.save!
|
||||||
|
process_attachments
|
||||||
|
@macro
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -25,10 +27,21 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attach_file
|
||||||
|
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||||
|
key: nil,
|
||||||
|
io: params[:attachment].tempfile,
|
||||||
|
filename: params[:attachment].original_filename,
|
||||||
|
content_type: params[:attachment].content_type
|
||||||
|
)
|
||||||
|
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
@macro.update!(macros_with_user)
|
@macro.update!(macros_with_user)
|
||||||
@macro.set_visibility(current_user, permitted_params)
|
@macro.set_visibility(current_user, permitted_params)
|
||||||
|
process_attachments
|
||||||
@macro.save!
|
@macro.save!
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error e
|
Rails.logger.error e
|
||||||
|
@ -42,6 +55,19 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_attachments
|
||||||
|
actions = @macro.actions.filter_map { |k, _v| k if k['action_name'] == 'send_attachment' }
|
||||||
|
return if actions.blank?
|
||||||
|
|
||||||
|
actions.each do |action|
|
||||||
|
blob_id = action['action_params']
|
||||||
|
blob = ActiveStorage::Blob.find_by(id: blob_id)
|
||||||
|
@macro.files.attach(blob)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(
|
params.permit(
|
||||||
:name, :account_id, :visibility,
|
:name, :account_id, :visibility,
|
||||||
|
@ -56,4 +82,8 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
def fetch_macro
|
def fetch_macro
|
||||||
@macro = Current.account.macros.find_by(id: params[:id])
|
@macro = Current.account.macros.find_by(id: params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_authorization
|
||||||
|
authorize(@macro) if @macro.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,10 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||||
@portal.members << agents
|
@portal.members << agents
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
@all_articles = @portal.articles
|
||||||
|
@articles = @all_articles.search(locale: params[:locale])
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@portal = Current.account.portals.build(portal_params)
|
@portal = Current.account.portals.build(portal_params)
|
||||||
|
|
|
@ -50,7 +50,9 @@ class Api::V1::Widget::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_name
|
def contact_name
|
||||||
params[:contact][:name] || contact_email.split('@')[0] if contact_email.present?
|
return if @contact.email.present? || @contact.phone_number.present? || @contact.identifier.present?
|
||||||
|
|
||||||
|
permitted_params.dig(:contact, :name) || (contact_email.split('@')[0] if contact_email.present?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_phone_number
|
def contact_phone_number
|
||||||
|
|
|
@ -17,7 +17,8 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||||
@contact = ContactIdentifyAction.new(
|
@contact = ContactIdentifyAction.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
params: { email: contact_email, phone_number: contact_phone_number, name: contact_name },
|
params: { email: contact_email, phone_number: contact_phone_number, name: contact_name },
|
||||||
retain_original_contact_name: true
|
retain_original_contact_name: true,
|
||||||
|
discard_invalid_attrs: true
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
class Platform::Api::V1::AccountsController < PlatformController
|
class Platform::Api::V1::AccountsController < PlatformController
|
||||||
def create
|
def create
|
||||||
@resource = Account.new(account_params)
|
@resource = Account.new(account_params)
|
||||||
|
update_resource_features
|
||||||
@resource.save!
|
@resource.save!
|
||||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||||
render json: @resource
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
render json: @resource
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@resource.update!(account_params)
|
@resource.assign_attributes(account_params)
|
||||||
render json: @resource
|
update_resource_features
|
||||||
|
@resource.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -27,6 +26,18 @@ class Platform::Api::V1::AccountsController < PlatformController
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:name, :locale)
|
permitted_params.except(:features)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_resource_features
|
||||||
|
return if permitted_params[:features].blank?
|
||||||
|
|
||||||
|
permitted_params[:features].each do |key, value|
|
||||||
|
value.present? ? @resource.enable_features(key) : @resource.disable_features(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:name, :locale, :domain, :support_email, :status, features: {}, limits: {}, custom_attributes: {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
|
||||||
@contact_inbox = ::ContactInboxWithContactBuilder.new(
|
@contact_inbox = ::ContactInboxWithContactBuilder.new(
|
||||||
source_id: source_id,
|
source_id: source_id,
|
||||||
inbox: @inbox_channel.inbox,
|
inbox: @inbox_channel.inbox,
|
||||||
contact_attributes: permitted_params.except(:identifier, :identifier_hash)
|
contact_attributes: permitted_params.except(:identifier_hash)
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,15 @@ class Public::Api::V1::InboxesController < PublicController
|
||||||
before_action :set_contact_inbox
|
before_action :set_contact_inbox
|
||||||
before_action :set_conversation
|
before_action :set_conversation
|
||||||
|
|
||||||
|
def show
|
||||||
|
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_inbox_channel
|
def set_inbox_channel
|
||||||
|
return if params[:inbox_id].blank?
|
||||||
|
|
||||||
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:inbox_id])
|
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:inbox_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class Public::Api::V1::Portals::ArticlesController < PublicController
|
class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
before_action :ensure_custom_domain_request, only: [:show, :index]
|
before_action :ensure_custom_domain_request, only: [:show, :index]
|
||||||
before_action :portal
|
before_action :portal
|
||||||
before_action :set_category
|
before_action :set_category, except: [:index]
|
||||||
before_action :set_article, only: [:show]
|
before_action :set_article, only: [:show]
|
||||||
layout 'portal'
|
layout 'portal'
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_category
|
def set_category
|
||||||
@category = @portal.categories.find_by!(slug: params[:category_slug])
|
@category = @portal.categories.find_by!(slug: params[:category_slug]) if params[:category_slug].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def portal
|
def portal
|
||||||
|
|
|
@ -8,6 +8,12 @@ module FileTypeHelper
|
||||||
:file
|
:file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used in case of DIRECT_UPLOADS_ENABLED=true
|
||||||
|
def file_type_by_signed_id(signed_id)
|
||||||
|
blob = ActiveStorage::Blob.find_signed(signed_id)
|
||||||
|
file_type(blob&.content_type)
|
||||||
|
end
|
||||||
|
|
||||||
def image_file?(content_type)
|
def image_file?(content_type)
|
||||||
[
|
[
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
|
|
|
@ -7,8 +7,8 @@ class CategoriesAPI extends PortalsAPI {
|
||||||
super('categories', { accountScoped: true });
|
super('categories', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
get({ portalSlug }) {
|
get({ portalSlug, locale }) {
|
||||||
return axios.get(`${this.url}/${portalSlug}/categories`);
|
return axios.get(`${this.url}/${portalSlug}/categories?locale=${locale}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
create({ portalSlug, categoryObj }) {
|
create({ portalSlug, categoryObj }) {
|
||||||
|
|
|
@ -6,6 +6,10 @@ class PortalsAPI extends ApiClient {
|
||||||
super('portals', { accountScoped: true });
|
super('portals', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPortal({ portalSlug, locale }) {
|
||||||
|
return axios.get(`${this.url}/${portalSlug}?locale=${locale}`);
|
||||||
|
}
|
||||||
|
|
||||||
updatePortal({ portalSlug, portalObj }) {
|
updatePortal({ portalSlug, portalObj }) {
|
||||||
return axios.patch(`${this.url}/${portalSlug}`, portalObj);
|
return axios.patch(`${this.url}/${portalSlug}`, portalObj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,16 @@ class ConversationApi extends ApiClient {
|
||||||
custom_attributes: customAttributes,
|
custom_attributes: customAttributes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchParticipants(conversationId) {
|
||||||
|
return axios.get(`${this.url}/${conversationId}/participants`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateParticipants({ conversationId, userIds }) {
|
||||||
|
return axios.patch(`${this.url}/${conversationId}/participants`, {
|
||||||
|
user_ids: userIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ConversationApi();
|
export default new ConversationApi();
|
||||||
|
|
|
@ -5,9 +5,12 @@ import Button from './ui/WootButton';
|
||||||
import Code from './Code';
|
import Code from './Code';
|
||||||
import ColorPicker from './widgets/ColorPicker';
|
import ColorPicker from './widgets/ColorPicker';
|
||||||
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
|
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
|
||||||
|
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
|
||||||
|
import ContextMenu from './ui/ContextMenu.vue';
|
||||||
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
||||||
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
||||||
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
||||||
|
import FeatureToggle from './widgets/FeatureToggle';
|
||||||
import HorizontalBar from './widgets/chart/HorizontalBarChart';
|
import HorizontalBar from './widgets/chart/HorizontalBarChart';
|
||||||
import Input from './widgets/forms/Input.vue';
|
import Input from './widgets/forms/Input.vue';
|
||||||
import Label from './ui/Label';
|
import Label from './ui/Label';
|
||||||
|
@ -21,8 +24,6 @@ import SubmitButton from './buttons/FormSubmitButton';
|
||||||
import Tabs from './ui/Tabs/Tabs';
|
import Tabs from './ui/Tabs/Tabs';
|
||||||
import TabsItem from './ui/Tabs/TabsItem';
|
import TabsItem from './ui/Tabs/TabsItem';
|
||||||
import Thumbnail from './widgets/Thumbnail.vue';
|
import Thumbnail from './widgets/Thumbnail.vue';
|
||||||
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
|
|
||||||
import ContextMenu from './ui/ContextMenu.vue';
|
|
||||||
|
|
||||||
const WootUIKit = {
|
const WootUIKit = {
|
||||||
AvatarUploader,
|
AvatarUploader,
|
||||||
|
@ -31,9 +32,12 @@ const WootUIKit = {
|
||||||
Code,
|
Code,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
ConfirmDeleteModal,
|
ConfirmDeleteModal,
|
||||||
|
ConfirmModal,
|
||||||
|
ContextMenu,
|
||||||
DeleteModal,
|
DeleteModal,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
FeatureToggle,
|
||||||
HorizontalBar,
|
HorizontalBar,
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
|
@ -47,8 +51,6 @@ const WootUIKit = {
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsItem,
|
TabsItem,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
ConfirmModal,
|
|
||||||
ContextMenu,
|
|
||||||
install(Vue) {
|
install(Vue) {
|
||||||
const keys = Object.keys(this);
|
const keys = Object.keys(this);
|
||||||
keys.pop(); // remove 'install' from keys
|
keys.pop(); // remove 'install' from keys
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
:class="{ 'text-truncate': shouldTruncate }"
|
:class="{ 'text-truncate': shouldTruncate }"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<span v-if="isHelpCenterSidebar && childItemCount" class="count-view">
|
<span v-if="showChildCount" class="count-view">
|
||||||
{{ childItemCount }}
|
{{ childItemCount }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -76,7 +76,7 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
isHelpCenterSidebar: {
|
showChildCount: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
@ -127,11 +127,16 @@ $label-badge-size: var(--space-slab);
|
||||||
color: var(--w-500);
|
color: var(--w-500);
|
||||||
border-color: var(--w-25);
|
border-color: var(--w-25);
|
||||||
}
|
}
|
||||||
|
&.is-active .count-view {
|
||||||
|
background: var(--w-75);
|
||||||
|
color: var(--w-500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-label {
|
.menu-label {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
line-height: var(--space-two);
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inbox-icon {
|
.inbox-icon {
|
||||||
|
@ -175,10 +180,6 @@ $label-badge-size: var(--space-slab);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
margin-left: var(--space-smaller);
|
margin-left: var(--space-smaller);
|
||||||
padding: var(--space-zero) var(--space-smaller);
|
padding: var(--space-zero) var(--space-smaller);
|
||||||
|
line-height: var(--font-size-small);
|
||||||
&.is-active {
|
|
||||||
background: var(--w-50);
|
|
||||||
color: var(--w-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,16 +4,15 @@
|
||||||
<span class="secondary-menu--header fs-small">
|
<span class="secondary-menu--header fs-small">
|
||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="isHelpCenterSidebar" class="submenu-icons">
|
<div v-if="menuItem.showNewButton" class="submenu-icons">
|
||||||
<woot-button
|
<woot-button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
|
icon="add"
|
||||||
class="submenu-icon"
|
class="submenu-icon"
|
||||||
@click="onClickOpen"
|
@click="onClickOpen"
|
||||||
>
|
/>
|
||||||
<fluent-icon icon="add" size="16" />
|
|
||||||
</woot-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -28,11 +27,7 @@
|
||||||
size="14"
|
size="14"
|
||||||
/>
|
/>
|
||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
<span
|
<span v-if="showChildCount(menuItem.count)" class="count-view">
|
||||||
v-if="isHelpCenterSidebar"
|
|
||||||
class="count-view"
|
|
||||||
:class="computedClass"
|
|
||||||
>
|
|
||||||
{{ `${menuItem.count}` }}
|
{{ `${menuItem.count}` }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -55,7 +50,7 @@
|
||||||
:should-truncate="child.truncateLabel"
|
:should-truncate="child.truncateLabel"
|
||||||
:icon="computedInboxClass(child)"
|
:icon="computedInboxClass(child)"
|
||||||
:warning-icon="computedInboxErrorClass(child)"
|
:warning-icon="computedInboxErrorClass(child)"
|
||||||
:is-help-center-sidebar="isHelpCenterSidebar"
|
:show-child-count="showChildCount(child.count)"
|
||||||
:child-item-count="child.count"
|
:child-item-count="child.count"
|
||||||
/>
|
/>
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -64,10 +59,10 @@
|
||||||
:to="menuItem.toState"
|
:to="menuItem.toState"
|
||||||
custom
|
custom
|
||||||
>
|
>
|
||||||
<li>
|
<li class="menu-item--new">
|
||||||
<a
|
<a
|
||||||
:href="href"
|
:href="href"
|
||||||
class="button small clear menu-item--new secondary"
|
class="button small link clear secondary"
|
||||||
:class="{ 'is-active': isActive }"
|
:class="{ 'is-active': isActive }"
|
||||||
@click="e => newLinkClick(e, navigate)"
|
@click="e => newLinkClick(e, navigate)"
|
||||||
>
|
>
|
||||||
|
@ -78,9 +73,6 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</router-link>
|
</router-link>
|
||||||
<p v-if="isHelpCenterSidebar && isCategoryEmpty" class="empty-text">
|
|
||||||
{{ $t('SIDEBAR.HELP_CENTER.CATEGORY_EMPTY_MESSAGE') }}
|
|
||||||
</p>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
@ -104,14 +96,6 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
isHelpCenterSidebar: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isCategoryEmpty: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
@ -161,8 +145,8 @@ export default {
|
||||||
this.menuItem.toStateName === 'settings_applications'
|
this.menuItem.toStateName === 'settings_applications'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isArticlesView() {
|
isCurrentRoute() {
|
||||||
return this.$store.state.route.name === this.menuItem.toStateName;
|
return this.$store.state.route.name.includes(this.menuItem.toStateName);
|
||||||
},
|
},
|
||||||
|
|
||||||
computedClass() {
|
computedClass() {
|
||||||
|
@ -181,12 +165,11 @@ export default {
|
||||||
}
|
}
|
||||||
return ' ';
|
return ' ';
|
||||||
}
|
}
|
||||||
if (this.isHelpCenterSidebar) {
|
|
||||||
if (this.isArticlesView) {
|
if (this.isCurrentRoute) {
|
||||||
return 'is-active';
|
return 'is-active';
|
||||||
}
|
|
||||||
return ' ';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -222,6 +205,9 @@ export default {
|
||||||
onClickOpen() {
|
onClickOpen() {
|
||||||
this.$emit('open');
|
this.$emit('open');
|
||||||
},
|
},
|
||||||
|
showChildCount(count) {
|
||||||
|
return Number.isInteger(count);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -277,6 +263,11 @@ export default {
|
||||||
color: var(--w-500);
|
color: var(--w-500);
|
||||||
border-color: var(--w-25);
|
border-color: var(--w-25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-active .count-view {
|
||||||
|
background: var(--w-75);
|
||||||
|
color: var(--w-600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-menu--icon {
|
.secondary-menu--icon {
|
||||||
|
@ -306,15 +297,12 @@ export default {
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-item .button.menu-item--new {
|
.sidebar-item .menu-item--new {
|
||||||
display: inline-flex;
|
padding: var(--space-small) 0;
|
||||||
height: var(--space-medium);
|
|
||||||
margin: var(--space-smaller) 0;
|
|
||||||
padding: var(--space-smaller);
|
|
||||||
color: var(--s-500);
|
|
||||||
|
|
||||||
&:hover {
|
.button {
|
||||||
color: var(--w-500);
|
display: inline-flex;
|
||||||
|
color: var(--s-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,11 +328,6 @@ export default {
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
margin-left: var(--space-smaller);
|
margin-left: var(--space-smaller);
|
||||||
padding: var(--space-zero) var(--space-smaller);
|
padding: var(--space-zero) var(--space-smaller);
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
background: var(--w-50);
|
|
||||||
color: var(--w-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submenu-icons {
|
.submenu-icons {
|
||||||
|
@ -356,10 +339,4 @@ export default {
|
||||||
margin-left: var(--space-small);
|
margin-left: var(--space-small);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
color: var(--s-500);
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
margin: var(--space-smaller);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="time-ago">
|
<span class="time-ago">
|
||||||
<span> {{ timeAgo }}</span>
|
<span>{{ timeAgo }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const ZERO = 0;
|
|
||||||
const MINUTE_IN_MILLI_SECONDS = 60000;
|
const MINUTE_IN_MILLI_SECONDS = 60000;
|
||||||
const HOUR_IN_MILLI_SECONDS = MINUTE_IN_MILLI_SECONDS * 60;
|
const HOUR_IN_MILLI_SECONDS = MINUTE_IN_MILLI_SECONDS * 60;
|
||||||
const DAY_IN_MILLI_SECONDS = HOUR_IN_MILLI_SECONDS * 24;
|
const DAY_IN_MILLI_SECONDS = HOUR_IN_MILLI_SECONDS * 24;
|
||||||
|
|
||||||
import timeMixin from 'dashboard/mixins/time';
|
import timeMixin from 'dashboard/mixins/time';
|
||||||
import { differenceInMilliseconds } from 'date-fns';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimeAgo',
|
name: 'TimeAgo',
|
||||||
|
@ -28,51 +26,40 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
timeAgo: '',
|
timeAgo: this.dynamicTime(this.timestamp),
|
||||||
timer: null,
|
timer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
watch: {
|
||||||
|
timestamp() {
|
||||||
|
this.timeAgo = this.dynamicTime(this.timestamp);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.isAutoRefreshEnabled) {
|
||||||
|
this.createTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createTimer() {
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.timeAgo = this.dynamicTime(this.timestamp);
|
||||||
|
this.createTimer();
|
||||||
|
}, this.refreshTime());
|
||||||
|
},
|
||||||
refreshTime() {
|
refreshTime() {
|
||||||
const timeDiff = differenceInMilliseconds(
|
const timeDiff = Date.now() - this.timestamp * 1000;
|
||||||
new Date(),
|
|
||||||
new Date(this.timestamp * 1000)
|
|
||||||
);
|
|
||||||
if (timeDiff > DAY_IN_MILLI_SECONDS) {
|
if (timeDiff > DAY_IN_MILLI_SECONDS) {
|
||||||
return DAY_IN_MILLI_SECONDS;
|
return DAY_IN_MILLI_SECONDS;
|
||||||
}
|
}
|
||||||
if (timeDiff > HOUR_IN_MILLI_SECONDS) {
|
if (timeDiff > HOUR_IN_MILLI_SECONDS) {
|
||||||
return HOUR_IN_MILLI_SECONDS;
|
return HOUR_IN_MILLI_SECONDS;
|
||||||
}
|
}
|
||||||
if (timeDiff > MINUTE_IN_MILLI_SECONDS) {
|
|
||||||
return MINUTE_IN_MILLI_SECONDS;
|
return MINUTE_IN_MILLI_SECONDS;
|
||||||
}
|
|
||||||
return ZERO;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.timeAgo = this.dynamicTime(this.timestamp);
|
|
||||||
if (this.isAutoRefreshEnabled) {
|
|
||||||
this.createTimer();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.clearTimer();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createTimer() {
|
|
||||||
const refreshTime = this.refreshTime;
|
|
||||||
if (refreshTime > ZERO) {
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
this.timeAgo = this.dynamicTime(this.timestamp);
|
|
||||||
this.createTimer();
|
|
||||||
}, refreshTime);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearTimer() {
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="filter" :class="actionInputStyles">
|
||||||
class="filter"
|
|
||||||
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
|
|
||||||
>
|
|
||||||
<div class="filter-inputs">
|
<div class="filter-inputs">
|
||||||
<select
|
<select
|
||||||
v-model="action_name"
|
v-model="action_name"
|
||||||
|
@ -21,14 +18,32 @@
|
||||||
<div v-if="showActionInput" class="filter__answer--wrap">
|
<div v-if="showActionInput" class="filter__answer--wrap">
|
||||||
<div v-if="inputType">
|
<div v-if="inputType">
|
||||||
<div
|
<div
|
||||||
v-if="inputType === 'multi_select'"
|
v-if="inputType === 'search_select'"
|
||||||
class="multiselect-wrap--small"
|
class="multiselect-wrap--small"
|
||||||
>
|
>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="action_params"
|
v-model="action_params"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
label="name"
|
label="name"
|
||||||
:placeholder="'Select'"
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
deselect-label=""
|
||||||
|
:max-height="160"
|
||||||
|
:options="dropdownValues"
|
||||||
|
:allow-empty="false"
|
||||||
|
:option-height="104"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="inputType === 'multi_select'"
|
||||||
|
class="multiselect-wrap--small"
|
||||||
|
>
|
||||||
|
<multiselect
|
||||||
|
v-model="action_params"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
selected-label
|
selected-label
|
||||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
@ -36,6 +51,7 @@
|
||||||
:max-height="160"
|
:max-height="160"
|
||||||
:options="dropdownValues"
|
:options="dropdownValues"
|
||||||
:allow-empty="false"
|
:allow-empty="false"
|
||||||
|
:option-height="104"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
@ -60,6 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
|
v-if="!isMacro"
|
||||||
icon="dismiss"
|
icon="dismiss"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
|
@ -120,6 +137,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
isMacro: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
action_name: {
|
action_name: {
|
||||||
|
@ -146,6 +167,12 @@ export default {
|
||||||
return this.actionTypes.find(action => action.key === this.action_name)
|
return this.actionTypes.find(action => action.key === this.action_name)
|
||||||
.inputType;
|
.inputType;
|
||||||
},
|
},
|
||||||
|
actionInputStyles() {
|
||||||
|
return {
|
||||||
|
'has-error': this.v.action_params.$dirty && this.v.action_params.$error,
|
||||||
|
'is-a-macro': this.isMacro,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeAction() {
|
removeAction() {
|
||||||
|
@ -165,9 +192,21 @@ export default {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius-medium);
|
border-radius: var(--border-radius-medium);
|
||||||
margin-bottom: var(--space-small);
|
margin-bottom: var(--space-small);
|
||||||
|
|
||||||
|
&.is-a-macro {
|
||||||
|
margin-bottom: 0;
|
||||||
|
background: var(--white);
|
||||||
|
padding: var(--space-zero);
|
||||||
|
border: unset;
|
||||||
|
border-radius: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter.error {
|
.no-margin-bottom {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter.has-error {
|
||||||
background: var(--r-50);
|
background: var(--r-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +279,6 @@ export default {
|
||||||
margin-bottom: var(--space-zero);
|
margin-bottom: var(--space-zero);
|
||||||
}
|
}
|
||||||
.action-message {
|
.action-message {
|
||||||
margin: var(--space-small) 0 0;
|
margin: var(--space-small) var(--space-zero) var(--space-zero);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -113,5 +113,6 @@ input[type='file'] {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="avatar-container" :style="style" aria-hidden="true">
|
||||||
class="avatar-container"
|
{{ userInitial }}
|
||||||
:style="[style, customStyle]"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<span>{{ userInitial }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,69 +12,26 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
backgroundColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#c2e1ff',
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: '#1976cc',
|
|
||||||
},
|
|
||||||
customStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
size: {
|
size: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 40,
|
default: 40,
|
||||||
},
|
},
|
||||||
src: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
rounded: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
variant: {
|
|
||||||
type: String,
|
|
||||||
default: 'circle',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style() {
|
style() {
|
||||||
let style = {
|
return {
|
||||||
width: `${this.size}px`,
|
|
||||||
height: `${this.size}px`,
|
|
||||||
borderRadius:
|
|
||||||
this.variant === 'square' ? 'var(--border-radius-large)' : '50%',
|
|
||||||
lineHeight: `${this.size + Math.floor(this.size / 20)}px`,
|
|
||||||
fontSize: `${Math.floor(this.size / 2.5)}px`,
|
fontSize: `${Math.floor(this.size / 2.5)}px`,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.backgroundColor) {
|
|
||||||
style = { ...style, backgroundColor: this.backgroundColor };
|
|
||||||
}
|
|
||||||
if (this.color) {
|
|
||||||
style = { ...style, color: this.color };
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
},
|
},
|
||||||
userInitial() {
|
userInitial() {
|
||||||
return this.initials || this.initial(this.username);
|
const parts = this.username.split(/[ -]/);
|
||||||
},
|
let initials = parts.reduce((acc, curr) => acc + curr.charAt(0), '');
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initial(username) {
|
|
||||||
const parts = username ? username.split(/[ -]/) : [];
|
|
||||||
let initials = '';
|
|
||||||
for (let i = 0; i < parts.length; i += 1) {
|
|
||||||
initials += parts[i].charAt(0);
|
|
||||||
}
|
|
||||||
if (initials.length > 2 && initials.search(/[A-Z]/) !== -1) {
|
if (initials.length > 2 && initials.search(/[A-Z]/) !== -1) {
|
||||||
initials = initials.replace(/[a-z]+/g, '');
|
initials = initials.replace(/[a-z]+/g, '');
|
||||||
}
|
}
|
||||||
initials = initials.substring(0, 2).toUpperCase();
|
initials = initials.substring(0, 2).toUpperCase();
|
||||||
|
|
||||||
return initials;
|
return initials;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -88,11 +41,13 @@ export default {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
line-height: 100%;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-image: linear-gradient(to top, var(--w-100) 0%, var(--w-75) 100%);
|
background-image: linear-gradient(to top, var(--w-100) 0%, var(--w-75) 100%);
|
||||||
color: var(--w-600);
|
color: var(--w-600);
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
class="dashboard-app--list"
|
class="dashboard-app--list"
|
||||||
>
|
>
|
||||||
|
<loading-state
|
||||||
|
v-if="iframeLoading"
|
||||||
|
:message="$t('DASHBOARD_APPS.LOADING_MESSAGE')"
|
||||||
|
class="dashboard-app_loading-container"
|
||||||
|
/>
|
||||||
<iframe
|
<iframe
|
||||||
v-if="configItem.type === 'frame' && configItem.url"
|
v-if="configItem.type === 'frame' && configItem.url"
|
||||||
:id="`dashboard-app--frame-${index}`"
|
:id="`dashboard-app--frame-${index}`"
|
||||||
|
@ -16,7 +21,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import LoadingState from 'dashboard/components/widgets/LoadingState';
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
LoadingState,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
config: {
|
config: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -27,6 +36,11 @@ export default {
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
iframeLoading: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
dashboardAppContext() {
|
dashboardAppContext() {
|
||||||
return {
|
return {
|
||||||
|
@ -57,6 +71,7 @@ export default {
|
||||||
);
|
);
|
||||||
const eventData = { event: 'appContext', data: this.dashboardAppContext };
|
const eventData = { event: 'appContext', data: this.dashboardAppContext };
|
||||||
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
|
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
|
||||||
|
this.iframeLoading = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -73,4 +88,11 @@ export default {
|
||||||
.dashboard-app--list iframe {
|
.dashboard-app--list iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
.dashboard-app_loading-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="isFeatureEnabled">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
featureKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
|
accountId: 'getCurrentAccountId',
|
||||||
|
}),
|
||||||
|
isFeatureEnabled() {
|
||||||
|
return this.isFeatureEnabledonAccount(this.accountId, this.featureKey);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -32,6 +32,7 @@
|
||||||
v-for="attribute in filterAttributes"
|
v-for="attribute in filterAttributes"
|
||||||
:key="attribute.key"
|
:key="attribute.key"
|
||||||
:value="attribute.key"
|
:value="attribute.key"
|
||||||
|
:disabled="attribute.disabled"
|
||||||
>
|
>
|
||||||
{{ attribute.name }}
|
{{ attribute.name }}
|
||||||
</option>
|
</option>
|
||||||
|
@ -173,6 +174,10 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
customAttributeType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
|
|
|
@ -83,75 +83,71 @@ export default {
|
||||||
},
|
},
|
||||||
pageSize: {
|
pageSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 15,
|
default: 25,
|
||||||
},
|
},
|
||||||
totalCount: {
|
totalCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
onPageChange: {
|
|
||||||
type: Function,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isFooterVisible() {
|
isFooterVisible() {
|
||||||
return this.totalCount && !(this.firstIndex > this.totalCount);
|
return this.totalCount && !(this.firstIndex > this.totalCount);
|
||||||
},
|
},
|
||||||
firstIndex() {
|
firstIndex() {
|
||||||
const firstIndex = this.pageSize * (this.currentPage - 1) + 1;
|
return this.pageSize * (this.currentPage - 1) + 1;
|
||||||
return firstIndex;
|
|
||||||
},
|
},
|
||||||
lastIndex() {
|
lastIndex() {
|
||||||
const index = Math.min(this.totalCount, this.pageSize * this.currentPage);
|
return Math.min(this.totalCount, this.pageSize * this.currentPage);
|
||||||
return index;
|
|
||||||
},
|
},
|
||||||
searchButtonClass() {
|
searchButtonClass() {
|
||||||
return this.searchQuery !== '' ? 'show' : '';
|
return this.searchQuery !== '' ? 'show' : '';
|
||||||
},
|
},
|
||||||
hasLastPage() {
|
hasLastPage() {
|
||||||
const isDisabled =
|
return !!Math.ceil(this.totalCount / this.pageSize);
|
||||||
this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
hasFirstPage() {
|
hasFirstPage() {
|
||||||
const isDisabled = this.currentPage === 1;
|
return this.currentPage === 1;
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
hasNextPage() {
|
hasNextPage() {
|
||||||
const isDisabled =
|
return this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
||||||
this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
hasPrevPage() {
|
hasPrevPage() {
|
||||||
const isDisabled = this.currentPage === 1;
|
return this.currentPage === 1;
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onNextPage() {
|
onNextPage() {
|
||||||
if (this.hasNextPage) return;
|
if (this.hasNextPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = this.currentPage + 1;
|
const newPage = this.currentPage + 1;
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
onPrevPage() {
|
onPrevPage() {
|
||||||
if (this.hasPrevPage) return;
|
if (this.hasPrevPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = this.currentPage - 1;
|
const newPage = this.currentPage - 1;
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
onFirstPage() {
|
onFirstPage() {
|
||||||
if (this.hasFirstPage) return;
|
if (this.hasFirstPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = 1;
|
const newPage = 1;
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
onLastPage() {
|
onLastPage() {
|
||||||
if (this.hasLastPage) return;
|
if (this.hasLastPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = Math.ceil(this.totalCount / this.pageSize);
|
const newPage = Math.ceil(this.totalCount / this.pageSize);
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
|
onPageChange(page) {
|
||||||
|
this.$emit('page-change', page);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,49 +2,47 @@ import { mount } from '@vue/test-utils';
|
||||||
import Avatar from './Avatar.vue';
|
import Avatar from './Avatar.vue';
|
||||||
import Thumbnail from './Thumbnail.vue';
|
import Thumbnail from './Thumbnail.vue';
|
||||||
|
|
||||||
describe(`when there are NO errors loading the thumbnail`, () => {
|
describe('Thumbnail.vue', () => {
|
||||||
it(`should render the agent thumbnail`, () => {
|
it('should render the agent thumbnail if valid image is passed', () => {
|
||||||
const wrapper = mount(Thumbnail, {
|
const wrapper = mount(Thumbnail, {
|
||||||
propsData: {
|
propsData: {
|
||||||
src: 'https://some_valid_url.com',
|
src: 'https://some_valid_url.com',
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasImageLoaded: true,
|
||||||
imgError: false,
|
imgError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.find('#image').exists()).toBe(true);
|
expect(wrapper.find('.user-thumbnail').exists()).toBe(true);
|
||||||
const avatarComponent = wrapper.findComponent(Avatar);
|
const avatarComponent = wrapper.findComponent(Avatar);
|
||||||
expect(avatarComponent.exists()).toBe(false);
|
expect(avatarComponent.isVisible()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe(`when there ARE errors loading the thumbnail`, () => {
|
it('should render the avatar component if invalid image is passed', () => {
|
||||||
it(`should render the agent avatar`, () => {
|
|
||||||
const wrapper = mount(Thumbnail, {
|
const wrapper = mount(Thumbnail, {
|
||||||
propsData: {
|
propsData: {
|
||||||
src: 'https://some_invalid_url.com',
|
src: 'https://some_invalid_url.com',
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasImageLoaded: true,
|
||||||
imgError: true,
|
imgError: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.find('#image').exists()).toBe(false);
|
expect(wrapper.find('#image').exists()).toBe(false);
|
||||||
const avatarComponent = wrapper.findComponent(Avatar);
|
const avatarComponent = wrapper.findComponent(Avatar);
|
||||||
expect(avatarComponent.exists()).toBe(true);
|
expect(avatarComponent.isVisible()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe(`when Avatar shows`, () => {
|
it('should the initial of the name if no image is passed', () => {
|
||||||
it(`initials shold correspond to username`, () => {
|
|
||||||
const wrapper = mount(Avatar, {
|
const wrapper = mount(Avatar, {
|
||||||
propsData: {
|
propsData: {
|
||||||
username: 'Angie Rojas',
|
username: 'Angie Rojas',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.find('span').text()).toBe('AR');
|
expect(wrapper.find('div').text()).toBe('AR');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,74 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-thumbnail-box" :style="{ height: size, width: size }">
|
<div
|
||||||
|
:class="thumbnailBoxClass"
|
||||||
|
:style="{ height: size, width: size }"
|
||||||
|
:title="title"
|
||||||
|
>
|
||||||
|
<!-- Using v-show instead of v-if to avoid flickering as v-if removes dom elements. -->
|
||||||
<img
|
<img
|
||||||
v-if="!imgError && Boolean(src)"
|
v-show="shouldShowImage"
|
||||||
id="image"
|
|
||||||
:src="src"
|
:src="src"
|
||||||
:class="thumbnailClass"
|
:class="thumbnailClass"
|
||||||
@error="onImgError()"
|
@load="onImgLoad"
|
||||||
|
@error="onImgError"
|
||||||
/>
|
/>
|
||||||
<Avatar
|
<Avatar
|
||||||
v-else
|
v-show="!shouldShowImage"
|
||||||
:username="userNameWithoutEmoji"
|
:username="userNameWithoutEmoji"
|
||||||
:class="thumbnailClass"
|
:class="thumbnailClass"
|
||||||
:size="avatarSize"
|
:size="avatarSize"
|
||||||
:variant="variant"
|
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'instagram_direct_message'"
|
v-if="badgeSrc"
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
class="source-badge"
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="/integrations/channels/badges/instagram-dm.png"
|
:src="`/integrations/channels/badges/${badgeSrc}.png`"
|
||||||
/>
|
alt="Badge"
|
||||||
<img
|
|
||||||
v-else-if="badge === 'facebook'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/messenger.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'twitter-tweet'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/twitter-tweet.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'twitter-dm'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/twitter-dm.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'whatsapp'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/whatsapp.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'sms'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/sms.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'Channel::Line'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/line.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'Channel::Telegram'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/telegram.png"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="showStatusIndicator"
|
v-if="showStatusIndicator"
|
||||||
|
@ -83,7 +38,7 @@
|
||||||
* Src - source for round image
|
* Src - source for round image
|
||||||
* Size - Size of the thumbnail
|
* Size - Size of the thumbnail
|
||||||
* Badge - Chat source indication { fb / telegram }
|
* Badge - Chat source indication { fb / telegram }
|
||||||
* Username - User name for avatar
|
* Username - Username for avatar
|
||||||
*/
|
*/
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import { removeEmoji } from 'shared/helpers/emoji';
|
import { removeEmoji } from 'shared/helpers/emoji';
|
||||||
|
@ -103,7 +58,7 @@ export default {
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'fb',
|
default: '',
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -121,6 +76,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
variant: {
|
variant: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'circle',
|
default: 'circle',
|
||||||
|
@ -128,6 +87,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasImageLoaded: false,
|
||||||
imgError: false,
|
imgError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -142,6 +102,19 @@ export default {
|
||||||
avatarSize() {
|
avatarSize() {
|
||||||
return Number(this.size.replace(/\D+/g, ''));
|
return Number(this.size.replace(/\D+/g, ''));
|
||||||
},
|
},
|
||||||
|
badgeSrc() {
|
||||||
|
return {
|
||||||
|
instagram_direct_message: 'instagram-dm',
|
||||||
|
facebook: 'messenger',
|
||||||
|
'twitter-tweet': 'twitter-tweet',
|
||||||
|
'twitter-dm': 'twitter-dm',
|
||||||
|
whatsapp: 'whatsapp',
|
||||||
|
sms: 'sms',
|
||||||
|
'Channel::Line': 'line',
|
||||||
|
'Channel::Telegram': 'telegram',
|
||||||
|
'Channel::WebWidget': '',
|
||||||
|
}[this.badge];
|
||||||
|
},
|
||||||
badgeStyle() {
|
badgeStyle() {
|
||||||
const size = Math.floor(this.avatarSize / 3);
|
const size = Math.floor(this.avatarSize / 3);
|
||||||
const badgeSize = `${size + 2}px`;
|
const badgeSize = `${size + 2}px`;
|
||||||
|
@ -158,20 +131,34 @@ export default {
|
||||||
this.variant === 'circle' ? 'thumbnail-rounded' : 'thumbnail-square';
|
this.variant === 'circle' ? 'thumbnail-rounded' : 'thumbnail-square';
|
||||||
return `user-thumbnail ${classname} ${variant}`;
|
return `user-thumbnail ${classname} ${variant}`;
|
||||||
},
|
},
|
||||||
|
thumbnailBoxClass() {
|
||||||
|
const boxClass = this.variant === 'circle' ? 'is-rounded' : '';
|
||||||
|
return `user-thumbnail-box ${boxClass}`;
|
||||||
|
},
|
||||||
|
shouldShowImage() {
|
||||||
|
if (!this.src) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.hasImageLoaded) {
|
||||||
|
return !this.imgError;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
src: {
|
src(value, oldValue) {
|
||||||
handler(value, oldValue) {
|
if (value !== oldValue && this.imgError) {
|
||||||
if (value !== oldValue && this.imgError) {
|
this.imgError = false;
|
||||||
this.imgError = false;
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onImgError() {
|
onImgError() {
|
||||||
this.imgError = true;
|
this.imgError = true;
|
||||||
},
|
},
|
||||||
|
onImgLoad() {
|
||||||
|
this.hasImageLoaded = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -182,6 +169,10 @@ export default {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&.is-rounded {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.user-thumbnail {
|
.user-thumbnail {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
&.thumbnail-square {
|
&.thumbnail-square {
|
||||||
|
@ -191,6 +182,7 @@ export default {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
vertical-align: initial;
|
||||||
|
|
||||||
&.border {
|
&.border {
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
|
@ -229,9 +221,5 @@ export default {
|
||||||
.user-online-status--offline {
|
.user-online-status--offline {
|
||||||
background: var(--s-500);
|
background: var(--s-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-online-status--offline {
|
|
||||||
background: var(--s-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<div class="overlapping-thumbnails">
|
||||||
|
<thumbnail
|
||||||
|
v-for="user in usersList"
|
||||||
|
:key="user.id"
|
||||||
|
v-tooltip="user.name"
|
||||||
|
:title="user.name"
|
||||||
|
:src="user.thumbnail"
|
||||||
|
:username="user.name"
|
||||||
|
:has-border="true"
|
||||||
|
:size="size"
|
||||||
|
:class="`overlapping-thumbnail gap-${gap}`"
|
||||||
|
/>
|
||||||
|
<span v-if="showMoreThumbnailsCount" class="thumbnail-more-text">
|
||||||
|
{{ moreThumbnailsText }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Thumbnail from './Thumbnail';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
usersList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '24px',
|
||||||
|
},
|
||||||
|
showMoreThumbnailsCount: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
moreThumbnailsText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
gap: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal',
|
||||||
|
validator(value) {
|
||||||
|
// The value must match one of these strings
|
||||||
|
return ['normal', '', 'tight'].includes(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.overlapping-thumbnails {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlapping-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: var(--space-minus-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-tight {
|
||||||
|
margin-left: var(--space-minus-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-more-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin-left: var(--space-minus-small);
|
||||||
|
padding: 0 var(--space-small);
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--space-giga);
|
||||||
|
border: 1px solid var(--white);
|
||||||
|
|
||||||
|
color: var(--s-600);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -42,7 +42,7 @@
|
||||||
</div>
|
</div>
|
||||||
<dashboard-app-frame
|
<dashboard-app-frame
|
||||||
v-else
|
v-else
|
||||||
:key="currentChat.id"
|
:key="currentChat.id + '-' + activeIndex"
|
||||||
:config="dashboardApps[activeIndex - 1].content"
|
:config="dashboardApps[activeIndex - 1].content"
|
||||||
:current-chat="currentChat"
|
:current-chat="currentChat"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -40,6 +40,12 @@
|
||||||
:url="attachment.data_url"
|
:url="attachment.data_url"
|
||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
/>
|
/>
|
||||||
|
<bubble-location
|
||||||
|
v-else-if="attachment.file_type === 'location'"
|
||||||
|
:latitude="attachment.coordinates_lat"
|
||||||
|
:longitude="attachment.coordinates_long"
|
||||||
|
:name="attachment.fallback_title"
|
||||||
|
/>
|
||||||
<bubble-file
|
<bubble-file
|
||||||
v-else
|
v-else
|
||||||
:url="attachment.data_url"
|
:url="attachment.data_url"
|
||||||
|
@ -119,6 +125,7 @@ import BubbleImage from './bubble/Image';
|
||||||
import BubbleFile from './bubble/File';
|
import BubbleFile from './bubble/File';
|
||||||
import BubbleVideo from './bubble/Video.vue';
|
import BubbleVideo from './bubble/Video.vue';
|
||||||
import BubbleActions from './bubble/Actions';
|
import BubbleActions from './bubble/Actions';
|
||||||
|
import BubbleLocation from './bubble/Location';
|
||||||
|
|
||||||
import Spinner from 'shared/components/Spinner';
|
import Spinner from 'shared/components/Spinner';
|
||||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||||
|
@ -136,6 +143,7 @@ export default {
|
||||||
BubbleFile,
|
BubbleFile,
|
||||||
BubbleVideo,
|
BubbleVideo,
|
||||||
BubbleMailHead,
|
BubbleMailHead,
|
||||||
|
BubbleLocation,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Spinner,
|
Spinner,
|
||||||
},
|
},
|
||||||
|
|
|
@ -139,7 +139,6 @@ export default {
|
||||||
listLoadingStatus: 'getAllMessagesLoaded',
|
listLoadingStatus: 'getAllMessagesLoaded',
|
||||||
getUnreadCount: 'getUnreadCount',
|
getUnreadCount: 'getUnreadCount',
|
||||||
loadingChatList: 'getChatListLoadingStatus',
|
loadingChatList: 'getChatListLoadingStatus',
|
||||||
conversationLastSeen: 'getConversationLastSeen',
|
|
||||||
}),
|
}),
|
||||||
inboxId() {
|
inboxId() {
|
||||||
return this.currentChat.inbox_id;
|
return this.currentChat.inbox_id;
|
||||||
|
@ -234,7 +233,6 @@ export default {
|
||||||
return 'arrow-chevron-left';
|
return 'arrow-chevron-left';
|
||||||
},
|
},
|
||||||
getLastSeenAt() {
|
getLastSeenAt() {
|
||||||
if (this.conversationLastSeen) return this.conversationLastSeen;
|
|
||||||
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
|
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
|
||||||
return contactLastSeenAt;
|
return contactLastSeenAt;
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<div class="location message-text__wrap">
|
||||||
|
<div class="icon-wrap">
|
||||||
|
<fluent-icon icon="location" class="file--icon" size="32" />
|
||||||
|
</div>
|
||||||
|
<div class="meta">
|
||||||
|
<h5 class="text-block-title text-truncate">
|
||||||
|
{{ name }}
|
||||||
|
</h5>
|
||||||
|
<div class="link-wrap">
|
||||||
|
<a
|
||||||
|
class="download clear link button small"
|
||||||
|
rel="noreferrer noopener nofollow"
|
||||||
|
target="_blank"
|
||||||
|
:href="mapUrl"
|
||||||
|
>
|
||||||
|
{{ $t('COMPONENTS.LOCATION_BUBBLE.SEE_ON_MAP') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
latitude: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
longitude: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
mapUrl() {
|
||||||
|
return `https://maps.google.com/?q=${this.latitude},${this.longitude}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.location {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: var(--space-smaller) 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon-wrap {
|
||||||
|
color: var(--s-600);
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0 var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-block-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--s-800);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-right: var(--space-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-wrap {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -17,7 +17,10 @@
|
||||||
@click="snoozeConversation(option.snoozedUntil)"
|
@click="snoozeConversation(option.snoozedUntil)"
|
||||||
/>
|
/>
|
||||||
</menu-item-with-submenu>
|
</menu-item-with-submenu>
|
||||||
<menu-item-with-submenu :option="labelMenuConfig">
|
<menu-item-with-submenu
|
||||||
|
:option="labelMenuConfig"
|
||||||
|
:sub-menu-available="!!labels.length"
|
||||||
|
>
|
||||||
<template>
|
<template>
|
||||||
<menu-item
|
<menu-item
|
||||||
v-for="label in labels"
|
v-for="label in labels"
|
||||||
|
@ -28,7 +31,10 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</menu-item-with-submenu>
|
</menu-item-with-submenu>
|
||||||
<menu-item-with-submenu :option="agentMenuConfig">
|
<menu-item-with-submenu
|
||||||
|
:option="agentMenuConfig"
|
||||||
|
:sub-menu-available="!!assignableAgents.length"
|
||||||
|
>
|
||||||
<agent-loading-placeholder v-if="assignableAgentsUiFlags.isFetching" />
|
<agent-loading-placeholder v-if="assignableAgentsUiFlags.isFetching" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<menu-item
|
<menu-item
|
||||||
|
@ -40,7 +46,10 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</menu-item-with-submenu>
|
</menu-item-with-submenu>
|
||||||
<menu-item-with-submenu :option="teamMenuConfig">
|
<menu-item-with-submenu
|
||||||
|
:option="teamMenuConfig"
|
||||||
|
:sub-menu-available="!!teams.length"
|
||||||
|
>
|
||||||
<menu-item
|
<menu-item
|
||||||
v-for="team in teams"
|
v-for="team in teams"
|
||||||
:key="team.id"
|
:key="team.id"
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
size="20px"
|
size="20px"
|
||||||
class="agent-thumbnail"
|
class="agent-thumbnail"
|
||||||
/>
|
/>
|
||||||
<p class="menu-label truncate-text">{{ option.label }}</p>
|
<p class="menu-label text-truncate">{{ option.label }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ export default {
|
||||||
padding: var(--space-smaller);
|
padding: var(--space-smaller);
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.menu-label {
|
.menu-label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="menu-with-submenu flex-between">
|
<div
|
||||||
|
class="menu-with-submenu flex-between"
|
||||||
|
:class="{ disabled: !subMenuAvailable }"
|
||||||
|
>
|
||||||
<div class="menu-left">
|
<div class="menu-left">
|
||||||
<fluent-icon :icon="option.icon" size="14" class="menu-icon" />
|
<fluent-icon :icon="option.icon" size="14" class="menu-icon" />
|
||||||
<p class="menu-label">{{ option.label }}</p>
|
<p class="menu-label">{{ option.label }}</p>
|
||||||
</div>
|
</div>
|
||||||
<fluent-icon icon="chevron-right" size="12" />
|
<fluent-icon icon="chevron-right" size="12" />
|
||||||
<div class="submenu">
|
<div v-if="subMenuAvailable" class="submenu">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +21,10 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
subMenuAvailable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -55,6 +62,11 @@ export default {
|
||||||
left: 100%;
|
left: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: none;
|
display: none;
|
||||||
|
min-height: min-content;
|
||||||
|
max-height: var(--space-giga);
|
||||||
|
overflow-y: auto;
|
||||||
|
// Need this because Firefox adds a horizontal scrollbar, if a text is truncated inside.
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -73,5 +85,10 @@ export default {
|
||||||
clip-path: polygon(100% 0, 0% 0%, 100% 100%);
|
clip-path: polygon(100% 0, 0% 0%, 100% 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 50%;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import LocationBubble from '../bubble/Location.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Help Center',
|
||||||
|
component: LocationBubble,
|
||||||
|
argTypes: {
|
||||||
|
latitude: {
|
||||||
|
defaultValue: 1,
|
||||||
|
control: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
longitude: {
|
||||||
|
defaultValue: 1,
|
||||||
|
control: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
defaultValue: '420, Dope street',
|
||||||
|
control: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: { LocationBubble },
|
||||||
|
template: '<location-bubble v-bind="$props" />',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LocationBubbleView = Template.bind({});
|
|
@ -0,0 +1,69 @@
|
||||||
|
import ThumbnailGroup from '../ThumbnailGroup.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/ThumbnailGroup',
|
||||||
|
component: ThumbnailGroup,
|
||||||
|
argTypes: {
|
||||||
|
usersList: {
|
||||||
|
defaultValue: [
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 1,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 2,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 3,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 4,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 5,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 6,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
control: {
|
||||||
|
type: 'object',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
moreThumbnailsText: {
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
default: '2 more',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showMoreThumbnailsCount: {
|
||||||
|
control: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: { ThumbnailGroup },
|
||||||
|
template: '<ThumbnailGroup v-bind="$props"/>',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
|
@ -25,6 +25,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
'notification.created': this.onNotificationCreated,
|
'notification.created': this.onNotificationCreated,
|
||||||
'first.reply.created': this.onFirstReplyCreated,
|
'first.reply.created': this.onFirstReplyCreated,
|
||||||
'conversation.read': this.onConversationRead,
|
'conversation.read': this.onConversationRead,
|
||||||
|
'conversation.updated': this.onConversationUpdated,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +68,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
};
|
};
|
||||||
|
|
||||||
onConversationRead = data => {
|
onConversationRead = data => {
|
||||||
const { contact_last_seen_at: lastSeen } = data;
|
this.app.$store.dispatch('updateConversation', data);
|
||||||
this.app.$store.dispatch('updateConversationRead', lastSeen);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onLogout = () => AuthAPI.logout();
|
onLogout = () => AuthAPI.logout();
|
||||||
|
@ -85,6 +85,11 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
this.fetchConversationStats();
|
this.fetchConversationStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onConversationUpdated = data => {
|
||||||
|
this.app.$store.dispatch('updateConversation', data);
|
||||||
|
this.fetchConversationStats();
|
||||||
|
};
|
||||||
|
|
||||||
onTypingOn = ({ conversation, user }) => {
|
onTypingOn = ({ conversation, user }) => {
|
||||||
const conversationId = conversation.id;
|
const conversationId = conversation.id;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const generatePayload = data => {
|
||||||
let payload = actions.map(item => {
|
let payload = actions.map(item => {
|
||||||
if (Array.isArray(item.action_params)) {
|
if (Array.isArray(item.action_params)) {
|
||||||
item.action_params = formatArray(item.action_params);
|
item.action_params = formatArray(item.action_params);
|
||||||
} else if (typeof item.values === 'object') {
|
} else if (typeof item.action_params === 'object') {
|
||||||
item.action_params = [item.action_params.id];
|
item.action_params = [item.action_params.id];
|
||||||
} else if (!item.action_params) {
|
} else if (!item.action_params) {
|
||||||
item.action_params = [];
|
item.action_params = [];
|
||||||
|
|
242
app/javascript/dashboard/helper/automationHelper.js
Normal file
242
app/javascript/dashboard/helper/automationHelper.js
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
import {
|
||||||
|
OPERATOR_TYPES_1,
|
||||||
|
OPERATOR_TYPES_3,
|
||||||
|
OPERATOR_TYPES_4,
|
||||||
|
} from 'dashboard/routes/dashboard/settings/automation/operators';
|
||||||
|
import filterQueryGenerator from './filterQueryGenerator';
|
||||||
|
import actionQueryGenerator from './actionQueryGenerator';
|
||||||
|
const MESSAGE_CONDITION_VALUES = [
|
||||||
|
{
|
||||||
|
id: 'incoming',
|
||||||
|
name: 'Incoming Message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'outgoing',
|
||||||
|
name: 'Outgoing Message',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getCustomAttributeInputType = key => {
|
||||||
|
const customAttributeMap = {
|
||||||
|
date: 'date',
|
||||||
|
text: 'plain_text',
|
||||||
|
list: 'search_select',
|
||||||
|
checkbox: 'search_select',
|
||||||
|
};
|
||||||
|
|
||||||
|
return customAttributeMap[key] || 'plain_text';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isACustomAttribute = (customAttributes, key) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return attr.attribute_key === key;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCustomAttributeListDropdownValues = (
|
||||||
|
customAttributes,
|
||||||
|
type
|
||||||
|
) => {
|
||||||
|
return customAttributes
|
||||||
|
.find(attr => attr.attribute_key === type)
|
||||||
|
.attribute_values.map(item => {
|
||||||
|
return {
|
||||||
|
id: item,
|
||||||
|
name: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttributeCheckbox = (customAttributes, key) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return (
|
||||||
|
attr.attribute_key === key && attr.attribute_display_type === 'checkbox'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttributeList = (customAttributes, type) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return (
|
||||||
|
attr.attribute_key === type && attr.attribute_display_type === 'list'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOperatorTypes = key => {
|
||||||
|
const operatorMap = {
|
||||||
|
list: OPERATOR_TYPES_1,
|
||||||
|
text: OPERATOR_TYPES_3,
|
||||||
|
number: OPERATOR_TYPES_1,
|
||||||
|
link: OPERATOR_TYPES_1,
|
||||||
|
date: OPERATOR_TYPES_4,
|
||||||
|
checkbox: OPERATOR_TYPES_1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return operatorMap[key] || OPERATOR_TYPES_1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCustomAttributeTypes = (customAttributes, type) => {
|
||||||
|
return customAttributes.map(attr => {
|
||||||
|
return {
|
||||||
|
key: attr.attribute_key,
|
||||||
|
name: attr.attribute_display_name,
|
||||||
|
inputType: getCustomAttributeInputType(attr.attribute_display_type),
|
||||||
|
filterOperators: getOperatorTypes(attr.attribute_display_type),
|
||||||
|
customAttributeType: type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateConditionOptions = (options, key = 'id') => {
|
||||||
|
return options.map(i => {
|
||||||
|
return {
|
||||||
|
id: i[key],
|
||||||
|
name: i.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActionOptions = ({ teams, labels, type }) => {
|
||||||
|
const actionsMap = {
|
||||||
|
assign_team: teams,
|
||||||
|
send_email_to_team: teams,
|
||||||
|
add_label: generateConditionOptions(labels, 'title'),
|
||||||
|
};
|
||||||
|
return actionsMap[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConditionOptions = ({
|
||||||
|
agents,
|
||||||
|
booleanFilterOptions,
|
||||||
|
campaigns,
|
||||||
|
contacts,
|
||||||
|
countries,
|
||||||
|
customAttributes,
|
||||||
|
inboxes,
|
||||||
|
languages,
|
||||||
|
statusFilterOptions,
|
||||||
|
teams,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
|
if (isCustomAttributeCheckbox(customAttributes, type)) {
|
||||||
|
return booleanFilterOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCustomAttributeList(customAttributes, type)) {
|
||||||
|
return getCustomAttributeListDropdownValues(customAttributes, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionFilterMaps = {
|
||||||
|
status: statusFilterOptions,
|
||||||
|
assignee_id: agents,
|
||||||
|
contact: contacts,
|
||||||
|
inbox_id: inboxes,
|
||||||
|
team_id: teams,
|
||||||
|
campaigns: generateConditionOptions(campaigns),
|
||||||
|
browser_language: languages,
|
||||||
|
country_code: countries,
|
||||||
|
message_type: MESSAGE_CONDITION_VALUES,
|
||||||
|
};
|
||||||
|
|
||||||
|
return conditionFilterMaps[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileName = (action, files = []) => {
|
||||||
|
const blobId = action.action_params[0];
|
||||||
|
if (!blobId) return '';
|
||||||
|
if (action.action_name === 'send_attachment') {
|
||||||
|
const file = files.find(item => item.blob_id === blobId);
|
||||||
|
if (file) return file.filename.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultConditions = eventName => {
|
||||||
|
if (eventName === 'message_created') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
attribute_key: 'message_type',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultActions = () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterCustomAttributes = customAttributes => {
|
||||||
|
return customAttributes.map(attr => {
|
||||||
|
return {
|
||||||
|
key: attr.attribute_key,
|
||||||
|
name: attr.attribute_display_name,
|
||||||
|
type: attr.attribute_display_type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStandardAttributeInputType = (automationTypes, event, key) => {
|
||||||
|
return automationTypes[event].conditions.find(item => item.key === key)
|
||||||
|
.inputType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateAutomationPayload = payload => {
|
||||||
|
const automation = JSON.parse(JSON.stringify(payload));
|
||||||
|
automation.conditions[automation.conditions.length - 1].query_operator = null;
|
||||||
|
automation.conditions = filterQueryGenerator(automation.conditions).payload;
|
||||||
|
automation.actions = actionQueryGenerator(automation.actions);
|
||||||
|
return automation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttribute = (attrs, key) => {
|
||||||
|
return attrs.find(attr => attr.key === key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCustomAttributes = (
|
||||||
|
conversationAttributes = [],
|
||||||
|
contactAttribtues = [],
|
||||||
|
conversationlabel,
|
||||||
|
contactlabel
|
||||||
|
) => {
|
||||||
|
const customAttributes = [];
|
||||||
|
if (conversationAttributes.length) {
|
||||||
|
customAttributes.push(
|
||||||
|
{
|
||||||
|
key: `conversation_custom_attribute`,
|
||||||
|
name: conversationlabel,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...conversationAttributes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (contactAttribtues.length) {
|
||||||
|
customAttributes.push(
|
||||||
|
{
|
||||||
|
key: `contact_custom_attribute`,
|
||||||
|
name: contactlabel,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...contactAttribtues
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return customAttributes;
|
||||||
|
};
|
|
@ -1,15 +1,3 @@
|
||||||
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 => {
|
const generatePayload = data => {
|
||||||
// Make a copy of data to avoid vue data reactivity issues
|
// Make a copy of data to avoid vue data reactivity issues
|
||||||
const filters = JSON.parse(JSON.stringify(data));
|
const filters = JSON.parse(JSON.stringify(data));
|
||||||
|
@ -23,8 +11,6 @@ const generatePayload = data => {
|
||||||
} else {
|
} else {
|
||||||
item.values = [item.values];
|
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;
|
return item;
|
||||||
});
|
});
|
||||||
// For every query added, the query_operator is set default to and so the
|
// For every query added, the query_operator is set default to and so the
|
||||||
|
|
|
@ -5,7 +5,7 @@ const testData = [
|
||||||
attribute_key: 'status',
|
attribute_key: 'status',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: [
|
values: [
|
||||||
{ id: 'PENDING', name: 'Pending' },
|
{ id: 'pending', name: 'Pending' },
|
||||||
{ id: 'resolved', name: 'Resolved' },
|
{ id: 'resolved', name: 'Resolved' },
|
||||||
],
|
],
|
||||||
query_operator: 'and',
|
query_operator: 'and',
|
||||||
|
@ -18,7 +18,7 @@ const testData = [
|
||||||
account_id: 1,
|
account_id: 1,
|
||||||
auto_offline: true,
|
auto_offline: true,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
email: 'fayazara@gmail.com',
|
email: 'fayaz@test.com',
|
||||||
available_name: 'Fayaz',
|
available_name: 'Fayaz',
|
||||||
name: 'Fayaz',
|
name: 'Fayaz',
|
||||||
role: 'agent',
|
role: 'agent',
|
||||||
|
@ -52,7 +52,7 @@ const finalResult = {
|
||||||
{
|
{
|
||||||
attribute_key: 'id',
|
attribute_key: 'id',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: ['this is a test'],
|
values: ['This is a test'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
122
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
122
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
export const teams = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '⚙️ sales team',
|
||||||
|
description: 'This is our internal sales team',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '🤷♂️ fayaz',
|
||||||
|
description: 'Test',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '🇮🇳 apac sales',
|
||||||
|
description: 'Sales team for France Territory',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const labels = [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'sales',
|
||||||
|
description: 'sales team',
|
||||||
|
color: '#8EA20F',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'billing',
|
||||||
|
description: 'billing',
|
||||||
|
color: '#4077DA',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'snoozed',
|
||||||
|
description: 'Items marked for later',
|
||||||
|
color: '#D12F42',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'mobile-app',
|
||||||
|
description: 'tech team',
|
||||||
|
color: '#2DB1CC',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
title: 'human-resources-department-with-long-title',
|
||||||
|
description: 'Test',
|
||||||
|
color: '#FF6E09',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 22,
|
||||||
|
title: 'priority',
|
||||||
|
description: 'For important sales leads',
|
||||||
|
color: '#7E7CED',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const agents = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
account_id: 1,
|
||||||
|
availability_status: 'offline',
|
||||||
|
auto_offline: true,
|
||||||
|
confirmed: true,
|
||||||
|
email: 'john@doe.com',
|
||||||
|
available_name: 'John Doe',
|
||||||
|
name: 'John Doe',
|
||||||
|
role: 'agent',
|
||||||
|
thumbnail:
|
||||||
|
'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--746506837470c1a3dd063e90211ba2386963d52f/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/batman_90804.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
account_id: 1,
|
||||||
|
availability_status: 'offline',
|
||||||
|
auto_offline: true,
|
||||||
|
confirmed: true,
|
||||||
|
email: 'clark@kent.com',
|
||||||
|
available_name: 'Clark Kent',
|
||||||
|
name: 'Clark Kent',
|
||||||
|
role: 'agent',
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const files = [
|
||||||
|
{
|
||||||
|
id: 76,
|
||||||
|
macro_id: 77,
|
||||||
|
file_type: 'image/jpeg',
|
||||||
|
account_id: 1,
|
||||||
|
file_url:
|
||||||
|
'http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBYUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--aa41b5a779a83c1d86b28475a5cf0bd17f41f0ff/fayaz_cropped.jpeg',
|
||||||
|
blob_id: 88,
|
||||||
|
filename: 'fayaz_cropped.jpeg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 82,
|
||||||
|
macro_id: 77,
|
||||||
|
file_type: 'image/png',
|
||||||
|
account_id: 1,
|
||||||
|
file_url:
|
||||||
|
'http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBZdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--260fda80b77409ffaaac10b96681fba447600545/screenshot.png',
|
||||||
|
blob_id: 94,
|
||||||
|
filename: 'screenshot.png',
|
||||||
|
},
|
||||||
|
];
|
75
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
75
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import {
|
||||||
|
emptyMacro,
|
||||||
|
resolveActionName,
|
||||||
|
resolveLabels,
|
||||||
|
resolveTeamIds,
|
||||||
|
getFileName,
|
||||||
|
resolveAgents,
|
||||||
|
} from '../../routes/dashboard/settings/macros/macroHelper';
|
||||||
|
import { MACRO_ACTION_TYPES } from '../../routes/dashboard/settings/macros/constants';
|
||||||
|
import { teams, labels, files, agents } from './macrosFixtures';
|
||||||
|
|
||||||
|
describe('#emptyMacro', () => {
|
||||||
|
const defaultMacro = {
|
||||||
|
name: '',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
visibility: 'global',
|
||||||
|
};
|
||||||
|
it('returns the default macro', () => {
|
||||||
|
expect(emptyMacro).toEqual(defaultMacro);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveActionName', () => {
|
||||||
|
it('resolve action name from key and return the correct label', () => {
|
||||||
|
expect(resolveActionName(MACRO_ACTION_TYPES[0].key)).toEqual(
|
||||||
|
MACRO_ACTION_TYPES[0].label
|
||||||
|
);
|
||||||
|
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).toEqual(
|
||||||
|
MACRO_ACTION_TYPES[1].label
|
||||||
|
);
|
||||||
|
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).not.toEqual(
|
||||||
|
MACRO_ACTION_TYPES[0].label
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveTeamIds', () => {
|
||||||
|
it('resolves team names from ids, and returns a joined string', () => {
|
||||||
|
const resolvedTeams = '⚙️ sales team, 🤷♂️ fayaz';
|
||||||
|
expect(resolveTeamIds(teams, [1, 2])).toEqual(resolvedTeams);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveLabels', () => {
|
||||||
|
it('resolves labels names from ids and returns a joined string', () => {
|
||||||
|
const resolvedLabels = 'sales, billing';
|
||||||
|
expect(resolveLabels(labels, ['sales', 'billing'])).toEqual(resolvedLabels);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveAgents', () => {
|
||||||
|
it('resolves agents names from ids and returns a joined string', () => {
|
||||||
|
const resolvedAgents = 'John Doe';
|
||||||
|
expect(resolveAgents(agents, [1])).toEqual(resolvedAgents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getFileName', () => {
|
||||||
|
it('returns the correct file name from the list of files', () => {
|
||||||
|
expect(getFileName(files[0].blob_id, 'send_attachment', files)).toEqual(
|
||||||
|
files[0].filename
|
||||||
|
);
|
||||||
|
expect(getFileName(files[1].blob_id, 'send_attachment', files)).toEqual(
|
||||||
|
files[1].filename
|
||||||
|
);
|
||||||
|
expect(getFileName(files[0].blob_id, 'wrong_action', files)).toEqual('');
|
||||||
|
expect(getFileName(null, 'send_attachment', files)).toEqual('');
|
||||||
|
expect(getFileName(files[0].blob_id, 'send_attachment', [])).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
5
app/javascript/dashboard/i18n/locale/ar/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/ar/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "تغيير نوع الحدث سوف يعيد تعيين الشروط والأحداث التي أضفتها أدناه"
|
"RESET_MESSAGE": "تغيير نوع الحدث سوف يعيد تعيين الشروط والأحداث التي أضفتها أدناه"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ"
|
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
|
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "تعذر تحميل المرفق، الرجاء المحاولة مرة أخرى",
|
"UPLOAD_ERROR": "تعذر تحميل المرفق، الرجاء المحاولة مرة أخرى",
|
||||||
"LABEL_IDLE": "ارفع المرفق",
|
"LABEL_IDLE": "ارفع المرفق",
|
||||||
"LABEL_UPLOADING": "جاري الرفع...",
|
"LABEL_UPLOADING": "جاري الرفع...",
|
||||||
"LABEL_UPLOADED": "تم الرفع بنجاح",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "فشل الرفع"
|
"LABEL_UPLOAD_FAILED": "فشل الرفع"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} المحادثات المحددة",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} المحادثات المحددة",
|
||||||
"AGENT_SELECT_LABEL": "اختر وكيل",
|
"AGENT_SELECT_LABEL": "اختر وكيل",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "هل أنت متأكد من أنك تريد تعيين %{conversationCount} %{conversationLabel} إلى",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "العودة للخلف",
|
"GO_BACK_LABEL": "العودة للخلف",
|
||||||
"ASSIGN_LABEL": "تكليف",
|
"ASSIGN_LABEL": "تكليف",
|
||||||
|
"YES": "نعم",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "إسناد وكيل",
|
"ASSIGN_AGENT_TOOLTIP": "إسناد وكيل",
|
||||||
"ASSIGN_SUCCESFUL": "تم تعيين المحادثات بنجاح",
|
"ASSIGN_SUCCESFUL": "تم تعيين المحادثات بنجاح",
|
||||||
"ASSIGN_FAILED": "فشل في تعيين المحادثات، الرجاء المحاولة مرة أخرى",
|
"ASSIGN_FAILED": "فشل في تعيين المحادثات، الرجاء المحاولة مرة أخرى",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "وسوم المحادثة",
|
"CONVERSATION_LABELS": "وسوم المحادثة",
|
||||||
"CONVERSATION_INFO": "معلومات المحادثة",
|
"CONVERSATION_INFO": "معلومات المحادثة",
|
||||||
"CONTACT_ATTRIBUTES": "سمات جهة الاتصال",
|
"CONTACT_ATTRIBUTES": "سمات جهة الاتصال",
|
||||||
"PREVIOUS_CONVERSATION": "المحادثات السابقة"
|
"PREVIOUS_CONVERSATION": "المحادثات السابقة",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "اضغط على زر الإدخال للاختيار",
|
"ENTER_TO_SELECT": "اضغط على زر الإدخال للاختيار",
|
||||||
"ENTER_TO_REMOVE": "اضغط على زر الإدخال للحذف",
|
"ENTER_TO_REMOVE": "اضغط على زر الإدخال للحذف",
|
||||||
"SELECT_ONE": "اختر واحدا"
|
"SELECT_ONE": "اختر واحدا",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "حتى الأسبوع القادم",
|
"UNTIL_NEXT_WEEK": "حتى الأسبوع القادم",
|
||||||
"UNTIL_TOMORROW": "حتى الغد"
|
"UNTIL_TOMORROW": "حتى الغد"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "نطاق مخصص",
|
"LABEL": "نطاق مخصص",
|
||||||
"PLACEHOLDER": "نطاق البوابة المخصص",
|
"PLACEHOLDER": "نطاق البوابة المخصص",
|
||||||
"HELP_TEXT": "أضف فقط إذا كنت ترغب في استخدام نطاق مخصص للبوابات الخاصة بك.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "النطاق المخصص مطلوب"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "رابط الصفحة الرئيسية",
|
"LABEL": "رابط الصفحة الرئيسية",
|
||||||
"PLACEHOLDER": "رابط الصفحة الرئيسية للبوابة",
|
"PLACEHOLDER": "رابط الصفحة الرئيسية للبوابة",
|
||||||
"HELP_TEXT": "الرابط المستخدم للعودة من البوابة إلى الصفحة الرئيسية.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "رابط الصفحة الرئيسية مطلوب"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "لون قالب البوابة",
|
"LABEL": "لون قالب البوابة",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "حدث خطأ أثناء نشر المقالة",
|
"ERROR": "حدث خطأ أثناء نشر المقالة",
|
||||||
"SUCCESS": "تم نشر المقالة بنجاح"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "عنوان Callback URL",
|
"TITLE": "عنوان Callback URL",
|
||||||
"SUBTITLE": "يجب عليك تكوين URL webhook في بوابة مطور فيسبوك مع عنوان URL المذكور هنا."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "رابط Webhook",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "إنشاء قناة واتساب",
|
"SUBMIT_BUTTON": "إنشاء قناة واتساب",
|
||||||
"API": {
|
"API": {
|
||||||
|
@ -357,7 +359,7 @@
|
||||||
},
|
},
|
||||||
"FINISH": {
|
"FINISH": {
|
||||||
"TITLE": "أصبحت قناة التواصل جاهزة الآن!",
|
"TITLE": "أصبحت قناة التواصل جاهزة الآن!",
|
||||||
"MESSAGE": "يمكنك الآن التواصل مع عملائك من خلال قناتك الجديدة ",
|
"MESSAGE": "يمكنك الآن التواصل مع عملائك من خلال قناتك الجديدة",
|
||||||
"BUTTON_TEXT": "خذني إلى هناك",
|
"BUTTON_TEXT": "خذني إلى هناك",
|
||||||
"MORE_SETTINGS": "المزيد من الإعدادات",
|
"MORE_SETTINGS": "المزيد من الإعدادات",
|
||||||
"WEBSITE_SUCCESS": "لقد انتهيت بنجاح من إنشاء قناة دردشة مباشرة لموقعك. انسخ الرمز الموضح أدناه وقم بإضافته إلى موقع الويب الخاص بك. في المرة القادمة التي يستخدم فيها العميل الدردشة المباشرة، ستظهر المحادثة تلقائياً على صندوق الوارد الخاص بك."
|
"WEBSITE_SUCCESS": "لقد انتهيت بنجاح من إنشاء قناة دردشة مباشرة لموقعك. انسخ الرمز الموضح أدناه وقم بإضافته إلى موقع الويب الخاص بك. في المرة القادمة التي يستخدم فيها العميل الدردشة المباشرة، ستظهر المحادثة تلقائياً على صندوق الوارد الخاص بك."
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/ar/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/ar/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "الإجراءات"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"الاسم",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "هل أنت متأكد من الحذف ",
|
||||||
|
"YES": "نعم، احذف",
|
||||||
|
"NO": "لا"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "تنزيل",
|
"DOWNLOAD": "تنزيل",
|
||||||
"UPLOADING": "جاري الرفع..."
|
"UPLOADING": "جاري الرفع..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "إرسال"
|
"SUBMIT": "إرسال"
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
"CONTACTS": "جهات الاتصال",
|
"CONTACTS": "جهات الاتصال",
|
||||||
"HOME": "الرئيسية",
|
"HOME": "الرئيسية",
|
||||||
"AGENTS": "موظف الدعم",
|
"AGENTS": "موظف الدعم",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "قنوات التواصل",
|
"INBOXES": "قنوات التواصل",
|
||||||
"NOTIFICATIONS": "الإشعارات",
|
"NOTIFICATIONS": "الإشعارات",
|
||||||
"CANNED_RESPONSES": "الردود السريعة",
|
"CANNED_RESPONSES": "الردود السريعة",
|
||||||
|
@ -189,6 +193,7 @@
|
||||||
"LABELS": "الوسوم",
|
"LABELS": "الوسوم",
|
||||||
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
||||||
"AUTOMATION": "الأتمتة",
|
"AUTOMATION": "الأتمتة",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "الفرق",
|
"TEAMS": "الفرق",
|
||||||
"BILLING": "الفواتير",
|
"BILLING": "الفواتير",
|
||||||
"CUSTOM_VIEWS_FOLDER": "المجلدات",
|
"CUSTOM_VIEWS_FOLDER": "المجلدات",
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/bg/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/bg/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||||
"LABEL_IDLE": "Upload Attachment",
|
"LABEL_IDLE": "Upload Attachment",
|
||||||
"LABEL_UPLOADING": "Качване...",
|
"LABEL_UPLOADING": "Качване...",
|
||||||
"LABEL_UPLOADED": "Succesfully Uploaded",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||||
"AGENT_SELECT_LABEL": "Select Agent",
|
"AGENT_SELECT_LABEL": "Select Agent",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Go back",
|
"GO_BACK_LABEL": "Go back",
|
||||||
"ASSIGN_LABEL": "Assign",
|
"ASSIGN_LABEL": "Assign",
|
||||||
|
"YES": "Yes",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Етикети на разговора",
|
"CONVERSATION_LABELS": "Етикети на разговора",
|
||||||
"CONVERSATION_INFO": "Conversation Information",
|
"CONVERSATION_INFO": "Conversation Information",
|
||||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||||
"PREVIOUS_CONVERSATION": "Предишни разговори"
|
"PREVIOUS_CONVERSATION": "Предишни разговори",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Press enter to select",
|
"ENTER_TO_SELECT": "Press enter to select",
|
||||||
"ENTER_TO_REMOVE": "Press enter to remove",
|
"ENTER_TO_REMOVE": "Press enter to remove",
|
||||||
"SELECT_ONE": "Select one"
|
"SELECT_ONE": "Select one",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "Until next week",
|
"UNTIL_NEXT_WEEK": "Until next week",
|
||||||
"UNTIL_TOMORROW": "Until tomorrow"
|
"UNTIL_TOMORROW": "Until tomorrow"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "Home Page Link",
|
"LABEL": "Home Page Link",
|
||||||
"PLACEHOLDER": "Portal home page link",
|
"PLACEHOLDER": "Portal home page link",
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "Portal theme color",
|
"LABEL": "Portal theme color",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "Error while publishing article",
|
"ERROR": "Error while publishing article",
|
||||||
"SUCCESS": "Article publishied successfully"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "Webhook URL",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"API": {
|
"API": {
|
||||||
|
@ -357,7 +359,7 @@
|
||||||
},
|
},
|
||||||
"FINISH": {
|
"FINISH": {
|
||||||
"TITLE": "Your Inbox is ready!",
|
"TITLE": "Your Inbox is ready!",
|
||||||
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting ",
|
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting",
|
||||||
"BUTTON_TEXT": "Take me there",
|
"BUTTON_TEXT": "Take me there",
|
||||||
"MORE_SETTINGS": "More settings",
|
"MORE_SETTINGS": "More settings",
|
||||||
"WEBSITE_SUCCESS": "You have successfully finished creating a website channel. Copy the code shown below and paste it on your website. Next time a customer use the live chat, the conversation will automatically appear on your inbox."
|
"WEBSITE_SUCCESS": "You have successfully finished creating a website channel. Copy the code shown below and paste it on your website. Next time a customer use the live chat, the conversation will automatically appear on your inbox."
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/bg/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/bg/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Действия"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Име",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "Сигурни ли сте за изтриването ",
|
||||||
|
"YES": "Да, изтрий",
|
||||||
|
"NO": "No"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
"UPLOADING": "Качване..."
|
"UPLOADING": "Качване..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "Изпращане"
|
"SUBMIT": "Изпращане"
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
"CONTACTS": "Контакти",
|
"CONTACTS": "Контакти",
|
||||||
"HOME": "Home",
|
"HOME": "Home",
|
||||||
"AGENTS": "Агенти",
|
"AGENTS": "Агенти",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "Inboxes",
|
"INBOXES": "Inboxes",
|
||||||
"NOTIFICATIONS": "Notifications",
|
"NOTIFICATIONS": "Notifications",
|
||||||
"CANNED_RESPONSES": "Готови отговори",
|
"CANNED_RESPONSES": "Готови отговори",
|
||||||
|
@ -189,6 +193,7 @@
|
||||||
"LABELS": "Labels",
|
"LABELS": "Labels",
|
||||||
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
|
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
|
||||||
"AUTOMATION": "Автоматизация",
|
"AUTOMATION": "Автоматизация",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "Teams",
|
"TEAMS": "Teams",
|
||||||
"BILLING": "Billing",
|
"BILLING": "Billing",
|
||||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
|
|
|
@ -1,48 +1,48 @@
|
||||||
{
|
{
|
||||||
"FILTER": {
|
"FILTER": {
|
||||||
"TITLE": "Filter Conversations",
|
"TITLE": "Filtre de converses",
|
||||||
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
|
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
|
||||||
"ADD_NEW_FILTER": "Add Filter",
|
"ADD_NEW_FILTER": "Afegeix filtre",
|
||||||
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
||||||
"SUBMIT_BUTTON_LABEL": "Apply filters",
|
"SUBMIT_BUTTON_LABEL": "Aplicar filtres",
|
||||||
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
||||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||||
"EMPTY_VALUE_ERROR": "Value is required",
|
"EMPTY_VALUE_ERROR": "El valor és necessari",
|
||||||
"TOOLTIP_LABEL": "Filter conversations",
|
"TOOLTIP_LABEL": "Filtre de converses",
|
||||||
"QUERY_DROPDOWN_LABELS": {
|
"QUERY_DROPDOWN_LABELS": {
|
||||||
"AND": "AND",
|
"AND": "I",
|
||||||
"OR": "OR"
|
"OR": "O"
|
||||||
},
|
},
|
||||||
"OPERATOR_LABELS": {
|
"OPERATOR_LABELS": {
|
||||||
"equal_to": "Equal to",
|
"equal_to": "Igual a",
|
||||||
"not_equal_to": "Not equal to",
|
"not_equal_to": "No és igual a",
|
||||||
"contains": "Contains",
|
"contains": "Conté",
|
||||||
"does_not_contain": "Does not contain",
|
"does_not_contain": "No conté",
|
||||||
"is_present": "Is present",
|
"is_present": "És present",
|
||||||
"is_not_present": "Is not present",
|
"is_not_present": "No és present",
|
||||||
"is_greater_than": "Is greater than",
|
"is_greater_than": "És més gran que",
|
||||||
"is_less_than": "Is lesser than",
|
"is_less_than": "És més petit que",
|
||||||
"days_before": "Is x days before"
|
"days_before": "Is x days before"
|
||||||
},
|
},
|
||||||
"ATTRIBUTE_LABELS": {
|
"ATTRIBUTE_LABELS": {
|
||||||
"TRUE": "True",
|
"TRUE": "Cert",
|
||||||
"FALSE": "False"
|
"FALSE": "Fals"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"STATUS": "Estat",
|
"STATUS": "Estat",
|
||||||
"ASSIGNEE_NAME": "Assignee Name",
|
"ASSIGNEE_NAME": "Assignee Name",
|
||||||
"INBOX_NAME": "Nom de la safata d'entrada",
|
"INBOX_NAME": "Nom de la safata d'entrada",
|
||||||
"TEAM_NAME": "Team Name",
|
"TEAM_NAME": "Nom de l'equip",
|
||||||
"CONVERSATION_IDENTIFIER": "Conversation Identifier",
|
"CONVERSATION_IDENTIFIER": "Identificador de la conversa",
|
||||||
"CAMPAIGN_NAME": "Campaign Name",
|
"CAMPAIGN_NAME": "Campaign Name",
|
||||||
"LABELS": "Etiquetes",
|
"LABELS": "Etiquetes",
|
||||||
"BROWSER_LANGUAGE": "Browser Language",
|
"BROWSER_LANGUAGE": "Browser Language",
|
||||||
"COUNTRY_NAME": "Country Name",
|
"COUNTRY_NAME": "Nom del país",
|
||||||
"REFERER_LINK": "Referer link",
|
"REFERER_LINK": "Referer link",
|
||||||
"CUSTOM_ATTRIBUTE_LIST": "List",
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
|
||||||
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
|
||||||
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
|
||||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
||||||
"CREATED_AT": "Created At",
|
"CREATED_AT": "Created At",
|
||||||
"LAST_ACTIVITY": "Last Activity"
|
"LAST_ACTIVITY": "Last Activity"
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/ca/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/ca/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,16 +96,16 @@
|
||||||
"PLACEHOLDER": "Ningú",
|
"PLACEHOLDER": "Ningú",
|
||||||
"TITLE": {
|
"TITLE": {
|
||||||
"AGENT": "Seleccionar Agent",
|
"AGENT": "Seleccionar Agent",
|
||||||
"TEAM": "Select team"
|
"TEAM": "Selecciona equip"
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"NO_RESULTS": {
|
"NO_RESULTS": {
|
||||||
"AGENT": "No s'han trobat agents",
|
"AGENT": "No s'han trobat agents",
|
||||||
"TEAM": "No teams found"
|
"TEAM": "No s'han trobat equips"
|
||||||
},
|
},
|
||||||
"PLACEHOLDER": {
|
"PLACEHOLDER": {
|
||||||
"AGENT": "Search agents",
|
"AGENT": "Cerca agents",
|
||||||
"TEAM": "Search teams"
|
"TEAM": "Cerca equips"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"ERROR": "Description is required"
|
"ERROR": "Description is required"
|
||||||
},
|
},
|
||||||
"EVENT": {
|
"EVENT": {
|
||||||
"LABEL": "Event",
|
"LABEL": "Esdeveniment",
|
||||||
"PLACEHOLDER": "Please select one",
|
"PLACEHOLDER": "Please select one",
|
||||||
"ERROR": "Event is required"
|
"ERROR": "Event is required"
|
||||||
},
|
},
|
||||||
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||||
"LABEL_IDLE": "Upload Attachment",
|
"LABEL_IDLE": "Upload Attachment",
|
||||||
"LABEL_UPLOADING": "S'està carregant...",
|
"LABEL_UPLOADING": "S'està carregant...",
|
||||||
"LABEL_UPLOADED": "Succesfully Uploaded",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||||
"AGENT_SELECT_LABEL": "Seleccionar Agent",
|
"AGENT_SELECT_LABEL": "Seleccionar Agent",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Go back",
|
"GO_BACK_LABEL": "Go back",
|
||||||
"ASSIGN_LABEL": "Assignar",
|
"ASSIGN_LABEL": "Assignar",
|
||||||
|
"YES": "Si",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SHORT_CODE": {
|
"SHORT_CODE": {
|
||||||
"LABEL": "Codi curt",
|
"LABEL": "Codi curt",
|
||||||
"PLACEHOLDER": "Please enter a short code",
|
"PLACEHOLDER": "Introduïu un codi curt",
|
||||||
"ERROR": "És necessari el codi curt"
|
"ERROR": "És necessari el codi curt"
|
||||||
},
|
},
|
||||||
"CONTENT": {
|
"CONTENT": {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"404": "No hi ha converses actives en aquest grup."
|
"404": "No hi ha converses actives en aquest grup."
|
||||||
},
|
},
|
||||||
"TAB_HEADING": "Converses",
|
"TAB_HEADING": "Converses",
|
||||||
"MENTION_HEADING": "Mentions",
|
"MENTION_HEADING": "Mencions",
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"INPUT": "Cerca persones, xats, respostes desades .."
|
"INPUT": "Cerca persones, xats, respostes desades .."
|
||||||
},
|
},
|
||||||
|
@ -25,10 +25,10 @@
|
||||||
"TEXT": "Resoltes"
|
"TEXT": "Resoltes"
|
||||||
},
|
},
|
||||||
"pending": {
|
"pending": {
|
||||||
"TEXT": "Pending"
|
"TEXT": "Pendent"
|
||||||
},
|
},
|
||||||
"snoozed": {
|
"snoozed": {
|
||||||
"TEXT": "Snoozed"
|
"TEXT": "Posposat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
"ATTACHMENTS": {
|
||||||
|
@ -54,12 +54,12 @@
|
||||||
"RECEIVED_VIA_EMAIL": "Rebut per correu electrònic",
|
"RECEIVED_VIA_EMAIL": "Rebut per correu electrònic",
|
||||||
"VIEW_TWEET_IN_TWITTER": "Veure el tuit a Twitter",
|
"VIEW_TWEET_IN_TWITTER": "Veure el tuit a Twitter",
|
||||||
"REPLY_TO_TWEET": "Respon a aquest tuit",
|
"REPLY_TO_TWEET": "Respon a aquest tuit",
|
||||||
"LINK_TO_STORY": "Go to instagram story",
|
"LINK_TO_STORY": "Ves a la història d'instagram",
|
||||||
"SENT": "Sent successfully",
|
"SENT": "Enviat correctament",
|
||||||
"NO_MESSAGES": "Cap Missatge",
|
"NO_MESSAGES": "Cap Missatge",
|
||||||
"NO_CONTENT": "No content available",
|
"NO_CONTENT": "No hi ha contingut disponible",
|
||||||
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
|
"HIDE_QUOTED_TEXT": "Amaga text entre cometes",
|
||||||
"SHOW_QUOTED_TEXT": "Show Quoted Text",
|
"SHOW_QUOTED_TEXT": "Mostra text entre cometes",
|
||||||
"MESSAGE_READ": "Read"
|
"MESSAGE_READ": "Llegir"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,40 +3,40 @@
|
||||||
"NOT_AVAILABLE": "No disponible",
|
"NOT_AVAILABLE": "No disponible",
|
||||||
"EMAIL_ADDRESS": "Adreça de correu electrònic",
|
"EMAIL_ADDRESS": "Adreça de correu electrònic",
|
||||||
"PHONE_NUMBER": "Número de telèfon",
|
"PHONE_NUMBER": "Número de telèfon",
|
||||||
"IDENTIFIER": "Identifier",
|
"IDENTIFIER": "Identificador",
|
||||||
"COPY_SUCCESSFUL": "S'ha copiat al porta-retalls amb èxit",
|
"COPY_SUCCESSFUL": "S'ha copiat al porta-retalls amb èxit",
|
||||||
"COMPANY": "Companyia",
|
"COMPANY": "Companyia",
|
||||||
"LOCATION": "Ubicació",
|
"LOCATION": "Ubicació",
|
||||||
"BROWSER_LANGUAGE": "Browser Language",
|
"BROWSER_LANGUAGE": "Idioma del navegador",
|
||||||
"CONVERSATION_TITLE": "Detalls de les converses",
|
"CONVERSATION_TITLE": "Detalls de les converses",
|
||||||
"VIEW_PROFILE": "View Profile",
|
"VIEW_PROFILE": "Veure perfil",
|
||||||
"BROWSER": "Navegador",
|
"BROWSER": "Navegador",
|
||||||
"OS": "Sistema operatiu",
|
"OS": "Sistema operatiu",
|
||||||
"INITIATED_FROM": "Iniciada des de",
|
"INITIATED_FROM": "Iniciada des de",
|
||||||
"INITIATED_AT": "Iniciada a les",
|
"INITIATED_AT": "Iniciada a les",
|
||||||
"IP_ADDRESS": "Adreça IP",
|
"IP_ADDRESS": "Adreça IP",
|
||||||
"NEW_MESSAGE": "New message",
|
"NEW_MESSAGE": "Nou missatge",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
|
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
|
||||||
"TITLE": "Converses prèvies"
|
"TITLE": "Converses prèvies"
|
||||||
},
|
},
|
||||||
"LABELS": {
|
"LABELS": {
|
||||||
"CONTACT": {
|
"CONTACT": {
|
||||||
"TITLE": "Contact Labels",
|
"TITLE": "Etiquetes de contactes",
|
||||||
"ERROR": "Couldn't update labels"
|
"ERROR": "No s'han pogut actualitzar les etiquetes"
|
||||||
},
|
},
|
||||||
"CONVERSATION": {
|
"CONVERSATION": {
|
||||||
"TITLE": "Etiquetes de converses",
|
"TITLE": "Etiquetes de converses",
|
||||||
"ADD_BUTTON": "Add Labels"
|
"ADD_BUTTON": "Afegir etiquetes"
|
||||||
},
|
},
|
||||||
"LABEL_SELECT": {
|
"LABEL_SELECT": {
|
||||||
"TITLE": "Add Labels",
|
"TITLE": "Afegir etiquetes",
|
||||||
"PLACEHOLDER": "Search labels",
|
"PLACEHOLDER": "Cerca etiquetes",
|
||||||
"NO_RESULT": "No labels found"
|
"NO_RESULT": "No s'han trobat etiquetes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MERGE_CONTACT": "Merge contact",
|
"MERGE_CONTACT": "Reagrupa contacte",
|
||||||
"CONTACT_ACTIONS": "Contact actions",
|
"CONTACT_ACTIONS": "Accions de contacte",
|
||||||
"MUTE_CONTACT": "Silencia la conversa",
|
"MUTE_CONTACT": "Silencia la conversa",
|
||||||
"UNMUTE_CONTACT": "Desactiva el silenci de la conversa",
|
"UNMUTE_CONTACT": "Desactiva el silenci de la conversa",
|
||||||
"MUTED_SUCCESS": "Aquesta conversa s'ha silenciat durant 6 hores",
|
"MUTED_SUCCESS": "Aquesta conversa s'ha silenciat durant 6 hores",
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"EDIT_LABEL": "Edita",
|
"EDIT_LABEL": "Edita",
|
||||||
"SIDEBAR_SECTIONS": {
|
"SIDEBAR_SECTIONS": {
|
||||||
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
||||||
"CONTACT_LABELS": "Contact Labels",
|
"CONTACT_LABELS": "Etiquetes de contactes",
|
||||||
"PREVIOUS_CONVERSATIONS": "Converses prèvies"
|
"PREVIOUS_CONVERSATIONS": "Converses prèvies"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -60,30 +60,30 @@
|
||||||
"DESC": "Afegir informació bàsica sobre el contacte."
|
"DESC": "Afegir informació bàsica sobre el contacte."
|
||||||
},
|
},
|
||||||
"IMPORT_CONTACTS": {
|
"IMPORT_CONTACTS": {
|
||||||
"BUTTON_LABEL": "Import",
|
"BUTTON_LABEL": "Importa",
|
||||||
"TITLE": "Import Contacts",
|
"TITLE": "Importa contactes",
|
||||||
"DESC": "Import contacts through a CSV file.",
|
"DESC": "Importa contactes a través d'un fitxer CSV.",
|
||||||
"DOWNLOAD_LABEL": "Download a sample csv.",
|
"DOWNLOAD_LABEL": "Descarrega un csv d'exemple.",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"LABEL": "CSV File",
|
"LABEL": "Fitxer CSV",
|
||||||
"SUBMIT": "Import",
|
"SUBMIT": "Importa",
|
||||||
"CANCEL": "Cancel·la"
|
"CANCEL": "Cancel·la"
|
||||||
},
|
},
|
||||||
"SUCCESS_MESSAGE": "Contacts saved successfully",
|
"SUCCESS_MESSAGE": "Contactes desat correctament",
|
||||||
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
||||||
},
|
},
|
||||||
"DELETE_NOTE": {
|
"DELETE_NOTE": {
|
||||||
"CONFIRM": {
|
"CONFIRM": {
|
||||||
"TITLE": "Confirma l'esborrat",
|
"TITLE": "Confirma l'esborrat",
|
||||||
"MESSAGE": "Are you want sure to delete this note?",
|
"MESSAGE": "Vols suprimir aquesta nota amb seguretat?",
|
||||||
"YES": "Yes, Delete it",
|
"YES": "Si, esborra'l",
|
||||||
"NO": "No, manten-la"
|
"NO": "No, manten-la"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DELETE_CONTACT": {
|
"DELETE_CONTACT": {
|
||||||
"BUTTON_LABEL": "Delete Contact",
|
"BUTTON_LABEL": "Contacte esborrat",
|
||||||
"TITLE": "Delete contact",
|
"TITLE": "Contacte esborrat",
|
||||||
"DESC": "Delete contact details",
|
"DESC": "Detalls del contacte esborrat",
|
||||||
"CONFIRM": {
|
"CONFIRM": {
|
||||||
"TITLE": "Confirma l'esborrat",
|
"TITLE": "Confirma l'esborrat",
|
||||||
"MESSAGE": "N'estas segur? ",
|
"MESSAGE": "N'estas segur? ",
|
||||||
|
@ -91,8 +91,8 @@
|
||||||
"NO": "No, segueix"
|
"NO": "No, segueix"
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"SUCCESS_MESSAGE": "Contact deleted successfully",
|
"SUCCESS_MESSAGE": "Contacte esborrat correctament",
|
||||||
"ERROR_MESSAGE": "Could not delete contact. Please try again later."
|
"ERROR_MESSAGE": "No s'ha pogut esborrar el contacte. Torneu-ho a provar."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONTACT_FORM": {
|
"CONTACT_FORM": {
|
||||||
|
@ -160,7 +160,7 @@
|
||||||
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
||||||
},
|
},
|
||||||
"NEW_CONVERSATION": {
|
"NEW_CONVERSATION": {
|
||||||
"BUTTON_LABEL": "Start conversation",
|
"BUTTON_LABEL": "Inicia la conversa",
|
||||||
"TITLE": "Nova conversació",
|
"TITLE": "Nova conversació",
|
||||||
"DESC": "Start a new conversation by sending a new message.",
|
"DESC": "Start a new conversation by sending a new message.",
|
||||||
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
||||||
|
|
|
@ -2,26 +2,26 @@
|
||||||
"CONTACTS_FILTER": {
|
"CONTACTS_FILTER": {
|
||||||
"TITLE": "Filter Contacts",
|
"TITLE": "Filter Contacts",
|
||||||
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
||||||
"ADD_NEW_FILTER": "Add Filter",
|
"ADD_NEW_FILTER": "Afegeix filtre",
|
||||||
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
||||||
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
||||||
"SUBMIT_BUTTON_LABEL": "Envia",
|
"SUBMIT_BUTTON_LABEL": "Envia",
|
||||||
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
||||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||||
"EMPTY_VALUE_ERROR": "Value is required",
|
"EMPTY_VALUE_ERROR": "El valor és necessari",
|
||||||
"TOOLTIP_LABEL": "Filter contacts",
|
"TOOLTIP_LABEL": "Filter contacts",
|
||||||
"QUERY_DROPDOWN_LABELS": {
|
"QUERY_DROPDOWN_LABELS": {
|
||||||
"AND": "AND",
|
"AND": "I",
|
||||||
"OR": "OR"
|
"OR": "O"
|
||||||
},
|
},
|
||||||
"OPERATOR_LABELS": {
|
"OPERATOR_LABELS": {
|
||||||
"equal_to": "Equal to",
|
"equal_to": "Igual a",
|
||||||
"not_equal_to": "Not equal to",
|
"not_equal_to": "No és igual a",
|
||||||
"contains": "Contains",
|
"contains": "Conté",
|
||||||
"does_not_contain": "Does not contain",
|
"does_not_contain": "No conté",
|
||||||
"is_present": "Is present",
|
"is_present": "És present",
|
||||||
"is_not_present": "Is not present",
|
"is_not_present": "No és present",
|
||||||
"is_greater_than": "Is greater than",
|
"is_greater_than": "És més gran que",
|
||||||
"is_lesser_than": "Is lesser than",
|
"is_lesser_than": "Is lesser than",
|
||||||
"days_before": "Is x days before"
|
"days_before": "Is x days before"
|
||||||
},
|
},
|
||||||
|
@ -33,9 +33,9 @@
|
||||||
"CITY": "City",
|
"CITY": "City",
|
||||||
"COUNTRY": "Country",
|
"COUNTRY": "Country",
|
||||||
"CUSTOM_ATTRIBUTE_LIST": "List",
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
|
||||||
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
|
||||||
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
|
||||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
||||||
"CREATED_AT": "Created At",
|
"CREATED_AT": "Created At",
|
||||||
"LAST_ACTIVITY": "Last Activity",
|
"LAST_ACTIVITY": "Last Activity",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Etiquetes de converses",
|
"CONVERSATION_LABELS": "Etiquetes de converses",
|
||||||
"CONVERSATION_INFO": "Conversation Information",
|
"CONVERSATION_INFO": "Conversation Information",
|
||||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||||
"PREVIOUS_CONVERSATION": "Converses prèvies"
|
"PREVIOUS_CONVERSATION": "Converses prèvies",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Presiona retorn (tecla enter) per seleccionar",
|
"ENTER_TO_SELECT": "Presiona retorn (tecla enter) per seleccionar",
|
||||||
"ENTER_TO_REMOVE": "Presiona retorn (tecla enter) per eliminar",
|
"ENTER_TO_REMOVE": "Presiona retorn (tecla enter) per eliminar",
|
||||||
"SELECT_ONE": "Selecciona un"
|
"SELECT_ONE": "Selecciona un",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "Until next week",
|
"UNTIL_NEXT_WEEK": "Until next week",
|
||||||
"UNTIL_TOMORROW": "Until tomorrow"
|
"UNTIL_TOMORROW": "Until tomorrow"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "Home Page Link",
|
"LABEL": "Home Page Link",
|
||||||
"PLACEHOLDER": "Portal home page link",
|
"PLACEHOLDER": "Portal home page link",
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "Portal theme color",
|
"LABEL": "Portal theme color",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "Error while publishing article",
|
"ERROR": "Error while publishing article",
|
||||||
"SUCCESS": "Article publishied successfully"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "URL del webhook",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"API": {
|
"API": {
|
||||||
|
@ -357,7 +359,7 @@
|
||||||
},
|
},
|
||||||
"FINISH": {
|
"FINISH": {
|
||||||
"TITLE": "La vostra safata d'entrada està a punt!",
|
"TITLE": "La vostra safata d'entrada està a punt!",
|
||||||
"MESSAGE": "Ja podeu interactuar amb els vostres clients a través del vostre canal nou. Feliç suport ",
|
"MESSAGE": "Ja podeu interactuar amb els vostres clients a través del vostre canal nou. Feliç suport",
|
||||||
"BUTTON_TEXT": "Porta'm allà",
|
"BUTTON_TEXT": "Porta'm allà",
|
||||||
"MORE_SETTINGS": "More settings",
|
"MORE_SETTINGS": "More settings",
|
||||||
"WEBSITE_SUCCESS": "Heu finalitzat amb èxit la creació d'un canal web. Copieu el codi que es mostra a continuació i enganxeu-lo al lloc web. La propera vegada que un client utilitzi el xat en directe, la conversa apareixerà automàticament a la safata d'entrada."
|
"WEBSITE_SUCCESS": "Heu finalitzat amb èxit la creació d'un canal web. Copieu el codi que es mostra a continuació i enganxeu-lo al lloc web. La propera vegada que un client utilitzi el xat en directe, la conversa apareixerà automàticament a la safata d'entrada."
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/ca/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/ca/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Accions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Nom",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "N'estas segur? ",
|
||||||
|
"YES": "Si, esborra",
|
||||||
|
"NO": "No"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "Descarrega",
|
"DOWNLOAD": "Descarrega",
|
||||||
"UPLOADING": "S'està carregant..."
|
"UPLOADING": "S'està carregant..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "Envia"
|
"SUBMIT": "Envia"
|
||||||
}
|
}
|
||||||
|
@ -173,12 +176,13 @@
|
||||||
"SWITCH": "Switch",
|
"SWITCH": "Switch",
|
||||||
"CONVERSATIONS": "Converses",
|
"CONVERSATIONS": "Converses",
|
||||||
"ALL_CONVERSATIONS": "All Conversations",
|
"ALL_CONVERSATIONS": "All Conversations",
|
||||||
"MENTIONED_CONVERSATIONS": "Mentions",
|
"MENTIONED_CONVERSATIONS": "Mencions",
|
||||||
"REPORTS": "Informes",
|
"REPORTS": "Informes",
|
||||||
"SETTINGS": "Configuracions",
|
"SETTINGS": "Configuracions",
|
||||||
"CONTACTS": "Contactes",
|
"CONTACTS": "Contactes",
|
||||||
"HOME": "Inici",
|
"HOME": "Inici",
|
||||||
"AGENTS": "Agents",
|
"AGENTS": "Agents",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "Safates d'entrada",
|
"INBOXES": "Safates d'entrada",
|
||||||
"NOTIFICATIONS": "Notificacions",
|
"NOTIFICATIONS": "Notificacions",
|
||||||
"CANNED_RESPONSES": "Respostes predeterminades",
|
"CANNED_RESPONSES": "Respostes predeterminades",
|
||||||
|
@ -189,14 +193,15 @@
|
||||||
"LABELS": "Etiquetes",
|
"LABELS": "Etiquetes",
|
||||||
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
||||||
"AUTOMATION": "Automation",
|
"AUTOMATION": "Automation",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "Equips",
|
"TEAMS": "Equips",
|
||||||
"BILLING": "Billing",
|
"BILLING": "Billing",
|
||||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
"CUSTOM_VIEWS_SEGMENTS": "Segments",
|
"CUSTOM_VIEWS_SEGMENTS": "Segments",
|
||||||
"ALL_CONTACTS": "All Contacts",
|
"ALL_CONTACTS": "All Contacts",
|
||||||
"TAGGED_WITH": "Tagged with",
|
"TAGGED_WITH": "Tagged with",
|
||||||
"NEW_LABEL": "New label",
|
"NEW_LABEL": "Nova etiqueta",
|
||||||
"NEW_TEAM": "New team",
|
"NEW_TEAM": "Nou equip",
|
||||||
"NEW_INBOX": "New inbox",
|
"NEW_INBOX": "New inbox",
|
||||||
"REPORTS_CONVERSATION": "Converses",
|
"REPORTS_CONVERSATION": "Converses",
|
||||||
"CSAT": "CSAT",
|
"CSAT": "CSAT",
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/cs/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/cs/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||||
"LABEL_IDLE": "Upload Attachment",
|
"LABEL_IDLE": "Upload Attachment",
|
||||||
"LABEL_UPLOADING": "Nahrávání...",
|
"LABEL_UPLOADING": "Nahrávání...",
|
||||||
"LABEL_UPLOADED": "Succesfully Uploaded",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||||
"AGENT_SELECT_LABEL": "Vybrat agenta",
|
"AGENT_SELECT_LABEL": "Vybrat agenta",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Go back",
|
"GO_BACK_LABEL": "Go back",
|
||||||
"ASSIGN_LABEL": "Přiřadit",
|
"ASSIGN_LABEL": "Přiřadit",
|
||||||
|
"YES": "Ano",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Štítky konverzace",
|
"CONVERSATION_LABELS": "Štítky konverzace",
|
||||||
"CONVERSATION_INFO": "Informace o konverzaci",
|
"CONVERSATION_INFO": "Informace o konverzaci",
|
||||||
"CONTACT_ATTRIBUTES": "Atributy kontaktu",
|
"CONTACT_ATTRIBUTES": "Atributy kontaktu",
|
||||||
"PREVIOUS_CONVERSATION": "Předchozí konverzace"
|
"PREVIOUS_CONVERSATION": "Předchozí konverzace",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Stiskněte Enter pro vybrání",
|
"ENTER_TO_SELECT": "Stiskněte Enter pro vybrání",
|
||||||
"ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání",
|
"ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání",
|
||||||
"SELECT_ONE": "Vyberte jeden"
|
"SELECT_ONE": "Vyberte jeden",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "Until next week",
|
"UNTIL_NEXT_WEEK": "Until next week",
|
||||||
"UNTIL_TOMORROW": "Until tomorrow"
|
"UNTIL_TOMORROW": "Until tomorrow"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "Home Page Link",
|
"LABEL": "Home Page Link",
|
||||||
"PLACEHOLDER": "Portal home page link",
|
"PLACEHOLDER": "Portal home page link",
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "Portal theme color",
|
"LABEL": "Portal theme color",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "Error while publishing article",
|
"ERROR": "Error while publishing article",
|
||||||
"SUCCESS": "Article publishied successfully"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "URL webového háčku",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"API": {
|
"API": {
|
||||||
|
@ -357,7 +359,7 @@
|
||||||
},
|
},
|
||||||
"FINISH": {
|
"FINISH": {
|
||||||
"TITLE": "Vaše doručená pošta je připravena!",
|
"TITLE": "Vaše doručená pošta je připravena!",
|
||||||
"MESSAGE": "Nyní se můžete spojit se svými zákazníky prostřednictvím nového kanálu. Šťastná podpora ",
|
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting",
|
||||||
"BUTTON_TEXT": "Vezmi mě tam",
|
"BUTTON_TEXT": "Vezmi mě tam",
|
||||||
"MORE_SETTINGS": "More settings",
|
"MORE_SETTINGS": "More settings",
|
||||||
"WEBSITE_SUCCESS": "Úspěšně jste dokončili vytvoření webového kanálu. Zkopírujte kód zobrazený níže a vložte jej na vaše webové stránky. Když zákazník příště použije živý chat, konverzace se automaticky objeví ve vaší doručené poště."
|
"WEBSITE_SUCCESS": "Úspěšně jste dokončili vytvoření webového kanálu. Zkopírujte kód zobrazený níže a vložte jej na vaše webové stránky. Když zákazník příště použije živý chat, konverzace se automaticky objeví ve vaší doručené poště."
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/cs/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/cs/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Akce"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Název",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "Opravdu chcete odstranit ",
|
||||||
|
"YES": "Ano, odstranit",
|
||||||
|
"NO": "Ne"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "Stáhnout",
|
"DOWNLOAD": "Stáhnout",
|
||||||
"UPLOADING": "Nahrávání..."
|
"UPLOADING": "Nahrávání..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "Odeslat"
|
"SUBMIT": "Odeslat"
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
"CONTACTS": "Kontakty",
|
"CONTACTS": "Kontakty",
|
||||||
"HOME": "Domů",
|
"HOME": "Domů",
|
||||||
"AGENTS": "Agenti",
|
"AGENTS": "Agenti",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "Schránky",
|
"INBOXES": "Schránky",
|
||||||
"NOTIFICATIONS": "Oznámení",
|
"NOTIFICATIONS": "Oznámení",
|
||||||
"CANNED_RESPONSES": "Konzervované odpovědi",
|
"CANNED_RESPONSES": "Konzervované odpovědi",
|
||||||
|
@ -189,6 +193,7 @@
|
||||||
"LABELS": "Štítky",
|
"LABELS": "Štítky",
|
||||||
"CUSTOM_ATTRIBUTES": "Vlastní atributy",
|
"CUSTOM_ATTRIBUTES": "Vlastní atributy",
|
||||||
"AUTOMATION": "Automation",
|
"AUTOMATION": "Automation",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "Týmy",
|
"TEAMS": "Týmy",
|
||||||
"BILLING": "Billing",
|
"BILLING": "Billing",
|
||||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/da/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/da/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Ændring af begivenhedstype vil nulstille de betingelser og begivenheder, du har tilføjet nedenfor"
|
"RESET_MESSAGE": "Ændring af begivenhedstype vil nulstille de betingelser og begivenheder, du har tilføjet nedenfor"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "Du skal have mindst én betingelse for at gemme"
|
"DELETE_MESSAGE": "Du skal have mindst én betingelse for at gemme",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "Du skal have mindst én handling for at gemme",
|
"DELETE_MESSAGE": "Du skal have mindst én handling for at gemme",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Kunne ikke uploade vedhæftning, Prøv venligst igen",
|
"UPLOAD_ERROR": "Kunne ikke uploade vedhæftning, Prøv venligst igen",
|
||||||
"LABEL_IDLE": "Upload Vedhæftning",
|
"LABEL_IDLE": "Upload Vedhæftning",
|
||||||
"LABEL_UPLOADING": "Uploader...",
|
"LABEL_UPLOADING": "Uploader...",
|
||||||
"LABEL_UPLOADED": "Succesfuldt Uploadet",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Mislykkedes"
|
"LABEL_UPLOAD_FAILED": "Upload Mislykkedes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} samtaler valgt",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} samtaler valgt",
|
||||||
"AGENT_SELECT_LABEL": "Vælg Agent",
|
"AGENT_SELECT_LABEL": "Vælg Agent",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Er du sikker på, at du vil tildele %{conversationCount} %{conversationLabel} til",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Gå tilbage",
|
"GO_BACK_LABEL": "Gå tilbage",
|
||||||
"ASSIGN_LABEL": "Tildel",
|
"ASSIGN_LABEL": "Tildel",
|
||||||
|
"YES": "Ja",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Tildel Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Tildel Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Samtaler tildelt",
|
"ASSIGN_SUCCESFUL": "Samtaler tildelt",
|
||||||
"ASSIGN_FAILED": "Mislykkedes at tildele samtaler, prøv igen",
|
"ASSIGN_FAILED": "Mislykkedes at tildele samtaler, prøv igen",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Samtale Etiketter",
|
"CONVERSATION_LABELS": "Samtale Etiketter",
|
||||||
"CONVERSATION_INFO": "Samtale Information",
|
"CONVERSATION_INFO": "Samtale Information",
|
||||||
"CONTACT_ATTRIBUTES": "Kontakt Attributter",
|
"CONTACT_ATTRIBUTES": "Kontakt Attributter",
|
||||||
"PREVIOUS_CONVERSATION": "Tidligere Samtaler"
|
"PREVIOUS_CONVERSATION": "Tidligere Samtaler",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue