mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-18 03:55:02 +00:00
Merge branch 'develop' into feat/ui-lib
This commit is contained in:
12
Gemfile.lock
12
Gemfile.lock
@@ -172,6 +172,8 @@ GEM
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 1.0)
|
||||
byebug (11.1.3)
|
||||
childprocess (5.1.0)
|
||||
logger (~> 1.5)
|
||||
climate_control (1.2.0)
|
||||
coderay (1.1.3)
|
||||
commonmarker (0.23.10)
|
||||
@@ -433,10 +435,12 @@ GEM
|
||||
json (>= 1.8)
|
||||
rexml
|
||||
language_server-protocol (3.17.0.5)
|
||||
launchy (2.5.2)
|
||||
launchy (3.1.1)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.8.1)
|
||||
launchy (>= 2.2, < 3)
|
||||
childprocess (~> 5.0)
|
||||
logger (~> 1.6)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
line-bot-api (1.28.0)
|
||||
lint_roller (1.1.0)
|
||||
liquid (5.4.0)
|
||||
@@ -563,7 +567,7 @@ GEM
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (6.0.0)
|
||||
public_suffix (6.0.2)
|
||||
puma (6.4.3)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.0)
|
||||
|
||||
@@ -10,7 +10,8 @@ function toggleSecretField(e) {
|
||||
if (!textElement) return;
|
||||
|
||||
if (textElement.dataset.secretMasked === 'false') {
|
||||
textElement.textContent = '•'.repeat(10);
|
||||
const maskedLength = secretField.dataset.secretText?.length || 10;
|
||||
textElement.textContent = '•'.repeat(maskedLength);
|
||||
textElement.dataset.secretMasked = 'true';
|
||||
toggler.querySelector('svg use').setAttribute('xlink:href', '#eye-show');
|
||||
|
||||
@@ -32,3 +33,13 @@ function copySecretField(e) {
|
||||
|
||||
navigator.clipboard.writeText(secretField.dataset.secretText);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.cell-data__secret-field').forEach(field => {
|
||||
const span = field.querySelector('[data-secret-masked]');
|
||||
if (span && span.dataset.secretMasked === 'true') {
|
||||
const len = field.dataset.secretText?.length || 10;
|
||||
span.textContent = '•'.repeat(len);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,17 +46,25 @@
|
||||
|
||||
.cell-data__secret-field {
|
||||
align-items: center;
|
||||
color: $hint-grey;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 5px;
|
||||
[data-secret-toggler],
|
||||
[data-secret-copier] {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
app/builders/v2/reports/label_summary_builder.rb
Normal file
103
app/builders/v2/reports/label_summary_builder.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
class V2::Reports::LabelSummaryBuilder < V2::Reports::BaseSummaryBuilder
|
||||
attr_reader :account, :params
|
||||
|
||||
# rubocop:disable Lint/MissingSuper
|
||||
# the parent class has no initialize
|
||||
def initialize(account:, params:)
|
||||
@account = account
|
||||
@params = params
|
||||
|
||||
timezone_offset = (params[:timezone_offset] || 0).to_f
|
||||
@timezone = ActiveSupport::TimeZone[timezone_offset]&.name
|
||||
end
|
||||
# rubocop:enable Lint/MissingSuper
|
||||
|
||||
def build
|
||||
labels = account.labels.to_a
|
||||
return [] if labels.empty?
|
||||
|
||||
report_data = collect_report_data
|
||||
labels.map { |label| build_label_report(label, report_data) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collect_report_data
|
||||
conversation_filter = build_conversation_filter
|
||||
use_business_hours = use_business_hours?
|
||||
|
||||
{
|
||||
conversation_counts: fetch_conversation_counts(conversation_filter),
|
||||
resolved_counts: fetch_resolved_counts(conversation_filter),
|
||||
resolution_metrics: fetch_metrics(conversation_filter, 'conversation_resolved', use_business_hours),
|
||||
first_response_metrics: fetch_metrics(conversation_filter, 'first_response', use_business_hours),
|
||||
reply_metrics: fetch_metrics(conversation_filter, 'reply_time', use_business_hours)
|
||||
}
|
||||
end
|
||||
|
||||
def build_label_report(label, report_data)
|
||||
{
|
||||
id: label.id,
|
||||
name: label.title,
|
||||
conversations_count: report_data[:conversation_counts][label.title] || 0,
|
||||
avg_resolution_time: report_data[:resolution_metrics][label.title] || 0,
|
||||
avg_first_response_time: report_data[:first_response_metrics][label.title] || 0,
|
||||
avg_reply_time: report_data[:reply_metrics][label.title] || 0,
|
||||
resolved_conversations_count: report_data[:resolved_counts][label.title] || 0
|
||||
}
|
||||
end
|
||||
|
||||
def use_business_hours?
|
||||
ActiveModel::Type::Boolean.new.cast(params[:business_hours])
|
||||
end
|
||||
|
||||
def build_conversation_filter
|
||||
conversation_filter = { account_id: account.id }
|
||||
conversation_filter[:created_at] = range if range.present?
|
||||
|
||||
conversation_filter
|
||||
end
|
||||
|
||||
def fetch_conversation_counts(conversation_filter)
|
||||
fetch_counts(conversation_filter)
|
||||
end
|
||||
|
||||
def fetch_resolved_counts(conversation_filter)
|
||||
# since the base query is ActsAsTaggableOn,
|
||||
# the status :resolved won't automatically be converted to integer status
|
||||
fetch_counts(conversation_filter.merge(status: Conversation.statuses[:resolved]))
|
||||
end
|
||||
|
||||
def fetch_counts(conversation_filter)
|
||||
ActsAsTaggableOn::Tagging
|
||||
.joins('INNER JOIN conversations ON taggings.taggable_id = conversations.id')
|
||||
.joins('INNER JOIN tags ON taggings.tag_id = tags.id')
|
||||
.where(
|
||||
taggable_type: 'Conversation',
|
||||
context: 'labels',
|
||||
conversations: conversation_filter
|
||||
)
|
||||
.select('tags.name, COUNT(taggings.*) AS count')
|
||||
.group('tags.name')
|
||||
.each_with_object({}) { |record, hash| hash[record.name] = record.count }
|
||||
end
|
||||
|
||||
def fetch_metrics(conversation_filter, event_name, use_business_hours)
|
||||
ReportingEvent
|
||||
.joins('INNER JOIN conversations ON reporting_events.conversation_id = conversations.id')
|
||||
.joins('INNER JOIN taggings ON taggings.taggable_id = conversations.id')
|
||||
.joins('INNER JOIN tags ON taggings.tag_id = tags.id')
|
||||
.where(
|
||||
conversations: conversation_filter,
|
||||
name: event_name,
|
||||
taggings: { taggable_type: 'Conversation', context: 'labels' }
|
||||
)
|
||||
.group('tags.name')
|
||||
.order('tags.name')
|
||||
.select(
|
||||
'tags.name',
|
||||
use_business_hours ? 'AVG(reporting_events.value_in_business_hours) as avg_value' : 'AVG(reporting_events.value) as avg_value'
|
||||
)
|
||||
.each_with_object({}) { |record, hash| hash[record.name] = record.avg_value.to_f }
|
||||
end
|
||||
end
|
||||
@@ -1,32 +1,23 @@
|
||||
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
|
||||
include GoogleConcern
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
email = params[:authorization][:email]
|
||||
redirect_url = google_client.auth_code.authorize_url(
|
||||
{
|
||||
redirect_uri: "#{base_url}/google/callback",
|
||||
scope: 'email profile https://mail.google.com/',
|
||||
scope: scope,
|
||||
response_type: 'code',
|
||||
prompt: 'consent', # the oauth flow does not return a refresh token, this is supposed to fix it
|
||||
access_type: 'offline', # the default is 'online'
|
||||
state: state,
|
||||
client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil)
|
||||
}
|
||||
)
|
||||
|
||||
if redirect_url
|
||||
cache_key = "google::#{email.downcase}"
|
||||
::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes)
|
||||
render json: { success: true, url: redirect_url }
|
||||
else
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,11 +81,15 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
|
||||
def create_channel
|
||||
return unless %w[web_widget api email line telegram whatsapp sms].include?(permitted_params[:channel][:type])
|
||||
return unless allowed_channel_types.include?(permitted_params[:channel][:type])
|
||||
|
||||
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
end
|
||||
|
||||
def allowed_channel_types
|
||||
%w[web_widget api email line telegram whatsapp sms]
|
||||
end
|
||||
|
||||
def update_inbox_working_hours
|
||||
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
|
||||
include InstagramConcern
|
||||
include Instagram::IntegrationHelper
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
# https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#step-1--get-authorization
|
||||
@@ -21,10 +20,4 @@ class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
|
||||
before_action :fetch_conversation, only: [:create_issue, :link_issue, :unlink_issue, :linked_issues]
|
||||
before_action :fetch_hook, only: [:destroy]
|
||||
|
||||
def destroy
|
||||
@@ -31,6 +31,12 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
Linear::ActivityMessageService.new(
|
||||
conversation: @conversation,
|
||||
action_type: :issue_created,
|
||||
issue_data: { id: issue[:data][:identifier] },
|
||||
user: Current.user
|
||||
).perform
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
@@ -42,17 +48,30 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
Linear::ActivityMessageService.new(
|
||||
conversation: @conversation,
|
||||
action_type: :issue_linked,
|
||||
issue_data: { id: issue_id },
|
||||
user: Current.user
|
||||
).perform
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def unlink_issue
|
||||
link_id = permitted_params[:link_id]
|
||||
issue_id = permitted_params[:issue_id]
|
||||
issue = linear_processor_service.unlink_issue(link_id)
|
||||
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
Linear::ActivityMessageService.new(
|
||||
conversation: @conversation,
|
||||
action_type: :issue_unlinked,
|
||||
issue_data: { id: issue_id },
|
||||
user: Current.user
|
||||
).perform
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
|
||||
include MicrosoftConcern
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
email = params[:authorization][:email]
|
||||
redirect_url = microsoft_client.auth_code.authorize_url(
|
||||
{
|
||||
redirect_uri: "#{base_url}/microsoft/callback",
|
||||
scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile',
|
||||
scope: scope,
|
||||
state: state,
|
||||
prompt: 'consent'
|
||||
}
|
||||
)
|
||||
if redirect_url
|
||||
cache_key = "microsoft::#{email.downcase}"
|
||||
::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes)
|
||||
render json: { success: true, url: redirect_url }
|
||||
else
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
class Api::V1::Accounts::OauthAuthorizationController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
|
||||
protected
|
||||
|
||||
def scope
|
||||
''
|
||||
end
|
||||
|
||||
def state
|
||||
Current.account.to_sgid(expires_in: 15.minutes).to_s
|
||||
end
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :prepare_builder_params, only: [:agent, :team, :inbox]
|
||||
before_action :prepare_builder_params, only: [:agent, :team, :inbox, :label]
|
||||
|
||||
def agent
|
||||
render_report_with(V2::Reports::AgentSummaryBuilder)
|
||||
@@ -14,6 +14,10 @@ class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseContr
|
||||
render_report_with(V2::Reports::InboxSummaryBuilder)
|
||||
end
|
||||
|
||||
def label
|
||||
render_report_with(V2::Reports::LabelSummaryBuilder)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
|
||||
@@ -14,7 +14,7 @@ module GoogleConcern
|
||||
|
||||
private
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
def scope
|
||||
'email profile https://mail.google.com/'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ module MicrosoftConcern
|
||||
|
||||
private
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
def scope
|
||||
'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile email'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ class DashboardController < ActionController::Base
|
||||
private
|
||||
|
||||
def ensure_html_format
|
||||
head :not_acceptable unless request.format.html?
|
||||
render json: { error: 'Please use API routes instead of dashboard routes for JSON requests' }, status: :not_acceptable if request.format.json?
|
||||
end
|
||||
|
||||
def set_global_config
|
||||
|
||||
@@ -6,7 +6,6 @@ class OauthCallbackController < ApplicationController
|
||||
)
|
||||
|
||||
handle_response
|
||||
::Redis::Alfred.delete(cache_key)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e).capture_exception
|
||||
redirect_to '/'
|
||||
@@ -64,10 +63,6 @@ class OauthCallbackController < ApplicationController
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def cache_key
|
||||
"#{provider_name}::#{users_data['email'].downcase}"
|
||||
end
|
||||
|
||||
def create_channel_with_inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
|
||||
@@ -86,12 +81,17 @@ class OauthCallbackController < ApplicationController
|
||||
decoded_token[0]
|
||||
end
|
||||
|
||||
def account_id
|
||||
::Redis::Alfred.get(cache_key)
|
||||
def account_from_signed_id
|
||||
raise ActionController::BadRequest, 'Missing state variable' if params[:state].blank?
|
||||
|
||||
account = GlobalID::Locator.locate_signed(params[:state])
|
||||
raise 'Invalid or expired state' if account.nil?
|
||||
|
||||
account
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= Account.find(account_id)
|
||||
@account ||= account_from_signed_id
|
||||
end
|
||||
|
||||
# Fallback name, for when name field is missing from users_data
|
||||
|
||||
@@ -7,7 +7,11 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
|
||||
def index
|
||||
@articles = @portal.articles.published.includes(:category, :author)
|
||||
|
||||
@articles = @articles.where(locale: permitted_params[:locale]) if permitted_params[:locale].present?
|
||||
|
||||
@articles_count = @articles.count
|
||||
|
||||
search_articles
|
||||
order_by_sort_param
|
||||
limit_results
|
||||
|
||||
@@ -36,9 +36,13 @@ module Api::V2::Accounts::ReportsHelper
|
||||
end
|
||||
|
||||
def generate_labels_report
|
||||
Current.account.labels.map do |label|
|
||||
label_report = report_builder({ type: :label, id: label.id }).short_summary
|
||||
[label.title] + generate_readable_report_metrics(label_report)
|
||||
reports = V2::Reports::LabelSummaryBuilder.new(
|
||||
account: Current.account,
|
||||
params: build_params({})
|
||||
).build
|
||||
|
||||
reports.map do |report|
|
||||
[report[:name]] + generate_readable_report_metrics(report)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -49,13 +49,7 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
profileUpdate({
|
||||
password,
|
||||
password_confirmation,
|
||||
displayName,
|
||||
avatar,
|
||||
...profileAttributes
|
||||
}) {
|
||||
profileUpdate({ displayName, avatar, ...profileAttributes }) {
|
||||
const formData = new FormData();
|
||||
Object.keys(profileAttributes).forEach(key => {
|
||||
const hasValue = profileAttributes[key] === undefined;
|
||||
@@ -64,16 +58,22 @@ export default {
|
||||
}
|
||||
});
|
||||
formData.append('profile[display_name]', displayName || '');
|
||||
if (password && password_confirmation) {
|
||||
formData.append('profile[password]', password);
|
||||
formData.append('profile[password_confirmation]', password_confirmation);
|
||||
}
|
||||
if (avatar) {
|
||||
formData.append('profile[avatar]', avatar);
|
||||
}
|
||||
return axios.put(endPoints('profileUpdate').url, formData);
|
||||
},
|
||||
|
||||
profilePasswordUpdate({ currentPassword, password, passwordConfirmation }) {
|
||||
return axios.put(endPoints('profileUpdate').url, {
|
||||
profile: {
|
||||
current_password: currentPassword,
|
||||
password,
|
||||
password_confirmation: passwordConfirmation,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
updateUISettings({ uiSettings }) {
|
||||
return axios.put(endPoints('profileUpdate').url, {
|
||||
profile: { ui_settings: uiSettings },
|
||||
|
||||
@@ -51,6 +51,7 @@ const endPoints = {
|
||||
resendConfirmation: {
|
||||
url: '/api/v1/profile/resend_confirmation',
|
||||
},
|
||||
|
||||
resetAccessToken: {
|
||||
url: '/api/v1/profile/reset_access_token',
|
||||
},
|
||||
|
||||
@@ -33,9 +33,11 @@ class LinearAPI extends ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
unlinkIssue(linkId) {
|
||||
unlinkIssue(linkId, issueIdentifier, conversationId) {
|
||||
return axios.post(`${this.url}/unlink_issue`, {
|
||||
link_id: linkId,
|
||||
issue_id: issueIdentifier,
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,19 @@ describe('#linearAPI', () => {
|
||||
issueData
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a valid request with conversation_id', () => {
|
||||
const issueData = {
|
||||
title: 'New Issue',
|
||||
description: 'Issue description',
|
||||
conversation_id: 123,
|
||||
};
|
||||
LinearAPIClient.createIssue(issueData);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/create_issue',
|
||||
issueData
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('link_issue', () => {
|
||||
@@ -120,6 +133,18 @@ describe('#linearAPI', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a valid request with title', () => {
|
||||
LinearAPIClient.link_issue(1, 'ENG-123', 'Sample Issue');
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/link_issue',
|
||||
{
|
||||
issue_id: 'ENG-123',
|
||||
conversation_id: 1,
|
||||
title: 'Sample Issue',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinkedIssue', () => {
|
||||
@@ -164,12 +189,26 @@ describe('#linearAPI', () => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.unlinkIssue(1);
|
||||
it('creates a valid request with link_id only', () => {
|
||||
LinearAPIClient.unlinkIssue('link123');
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/unlink_issue',
|
||||
{
|
||||
link_id: 1,
|
||||
link_id: 'link123',
|
||||
issue_id: undefined,
|
||||
conversation_id: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a valid request with all parameters', () => {
|
||||
LinearAPIClient.unlinkIssue('link123', 'ENG-456', 789);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/unlink_issue',
|
||||
{
|
||||
link_id: 'link123',
|
||||
issue_id: 'ENG-456',
|
||||
conversation_id: 789,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -35,6 +35,16 @@ class SummaryReportsAPI extends ApiClient {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getLabelReports({ since, until, businessHours } = {}) {
|
||||
return axios.get(`${this.url}/label`, {
|
||||
params: {
|
||||
since,
|
||||
until,
|
||||
business_hours: businessHours,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SummaryReportsAPI();
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
@apply max-w-full;
|
||||
|
||||
.multiselect__option {
|
||||
@apply text-sm font-normal;
|
||||
@apply text-sm font-normal flex justify-between items-center;
|
||||
|
||||
span {
|
||||
@apply inline-block overflow-hidden text-ellipsis whitespace-nowrap w-fit;
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
|
||||
&::after {
|
||||
@apply bottom-0 flex items-center justify-center text-center;
|
||||
@apply bottom-0 flex items-center justify-center text-center relative px-1 leading-tight;
|
||||
}
|
||||
|
||||
&.multiselect__option--highlight {
|
||||
@@ -74,7 +74,7 @@
|
||||
}
|
||||
|
||||
&.multiselect__option--highlight::after {
|
||||
@apply bg-transparent;
|
||||
@apply bg-transparent text-n-slate-12;
|
||||
}
|
||||
|
||||
&.multiselect__option--selected {
|
||||
|
||||
@@ -123,7 +123,7 @@ const handleDocumentableClick = () => {
|
||||
@mouseenter="emit('hover', true)"
|
||||
@mouseleave="emit('hover', false)"
|
||||
>
|
||||
<div v-show="selectable" class="absolute top-7 ltr:left-4 rtl:right-4">
|
||||
<div v-show="selectable" class="absolute top-7 ltr:left-3 rtl:right-3">
|
||||
<Checkbox v-model="modelValue" />
|
||||
</div>
|
||||
<div class="flex relative justify-between w-full gap-1">
|
||||
|
||||
@@ -19,6 +19,7 @@ const isConversationRoute = computed(() => {
|
||||
'conversation_through_mentions',
|
||||
'conversation_through_unattended',
|
||||
'conversation_through_participating',
|
||||
'inbox_view_conversation',
|
||||
];
|
||||
return CONVERSATION_ROUTES.includes(route.name);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ export function useChannelIcon(inbox) {
|
||||
'Channel::WebWidget': 'i-ri-global-fill',
|
||||
'Channel::Whatsapp': 'i-ri-whatsapp-fill',
|
||||
'Channel::Instagram': 'i-ri-instagram-fill',
|
||||
'Channel::Voice': 'i-ri-phone-fill',
|
||||
};
|
||||
|
||||
const providerIconMap = {
|
||||
|
||||
@@ -19,6 +19,12 @@ describe('useChannelIcon', () => {
|
||||
expect(icon).toBe('i-ri-whatsapp-fill');
|
||||
});
|
||||
|
||||
it('returns correct icon for Voice channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Voice' };
|
||||
const { value: icon } = useChannelIcon(inbox);
|
||||
expect(icon).toBe('i-ri-phone-fill');
|
||||
});
|
||||
|
||||
describe('Email channel', () => {
|
||||
it('returns mail icon for generic email channel', () => {
|
||||
const inbox = { channel_type: 'Channel::Email' };
|
||||
|
||||
@@ -1,51 +1,21 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, nextTick } from 'vue';
|
||||
import { computed, ref, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
customInputClass: {
|
||||
type: [String, Object, Array],
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
modelValue: { type: [String, Number], default: '' },
|
||||
type: { type: String, default: 'text' },
|
||||
customInputClass: { type: [String, Object, Array], default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
label: { type: String, default: '' },
|
||||
id: { type: String, default: '' },
|
||||
message: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
messageType: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: value => ['info', 'error', 'success'].includes(value),
|
||||
},
|
||||
min: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
min: { type: String, default: '' },
|
||||
autofocus: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -56,6 +26,10 @@ const emit = defineEmits([
|
||||
'enter',
|
||||
]);
|
||||
|
||||
// Generate a unique ID per component instance when `id` prop is not provided.
|
||||
const { uid } = getCurrentInstance();
|
||||
const uniqueId = computed(() => props.id || `input-${uid}`);
|
||||
|
||||
const isFocused = ref(false);
|
||||
const inputRef = ref(null);
|
||||
|
||||
@@ -111,7 +85,7 @@ onMounted(() => {
|
||||
<div class="relative flex flex-col min-w-0 gap-1">
|
||||
<label
|
||||
v-if="label"
|
||||
:for="id"
|
||||
:for="uniqueId"
|
||||
class="mb-0.5 text-sm font-medium text-n-slate-12"
|
||||
>
|
||||
{{ label }}
|
||||
@@ -119,7 +93,7 @@ onMounted(() => {
|
||||
<!-- Added prefix slot to allow adding icons to the input -->
|
||||
<slot name="prefix" />
|
||||
<input
|
||||
:id="id"
|
||||
:id="uniqueId"
|
||||
ref="inputRef"
|
||||
:value="modelValue"
|
||||
:class="[
|
||||
|
||||
@@ -379,7 +379,7 @@ const shouldRenderMessage = computed(() => {
|
||||
function openContextMenu(e) {
|
||||
const shouldSkipContextMenu =
|
||||
e.target?.classList.contains('skip-context-menu') ||
|
||||
e.target?.tagName.toLowerCase() === 'a';
|
||||
['a', 'img'].includes(e.target?.tagName.toLowerCase());
|
||||
if (shouldSkipContextMenu || getSelection().toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ const fromEmail = computed(() => {
|
||||
});
|
||||
|
||||
const toEmail = computed(() => {
|
||||
return contentAttributes.value?.email?.to ?? [];
|
||||
const { toEmails, email } = contentAttributes.value;
|
||||
return email?.to ?? toEmails ?? [];
|
||||
});
|
||||
|
||||
const ccEmail = computed(() => {
|
||||
|
||||
@@ -4,9 +4,11 @@ import BaseBubble from './Base.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { CONTENT_TYPES } from '../constants.js';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import { useInbox } from 'dashboard/composables/useInbox';
|
||||
|
||||
const { content, contentAttributes, contentType } = useMessageContext();
|
||||
const { t } = useI18n();
|
||||
const { isAWebWidgetInbox } = useInbox();
|
||||
|
||||
const formValues = computed(() => {
|
||||
if (contentType.value === CONTENT_TYPES.FORM) {
|
||||
@@ -56,7 +58,7 @@ const formValues = computed(() => {
|
||||
<dd>{{ item.title }}</dd>
|
||||
</template>
|
||||
</dl>
|
||||
<div v-else class="my-2 font-medium">
|
||||
<div v-else-if="isAWebWidgetInbox" class="my-2 font-medium">
|
||||
{{ t('CONVERSATION.NO_RESPONSE') }}
|
||||
</div>
|
||||
</BaseBubble>
|
||||
|
||||
@@ -87,7 +87,7 @@ const newReportRoutes = () => [
|
||||
{
|
||||
name: 'Reports Label',
|
||||
label: t('SIDEBAR.REPORTS_LABEL'),
|
||||
to: accountScopedRoute('label_reports'),
|
||||
to: accountScopedRoute('label_reports_index'),
|
||||
},
|
||||
{
|
||||
name: 'Reports Inbox',
|
||||
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
<button
|
||||
class="bg-white dark:bg-slate-900 cursor-pointer flex flex-col justify-end transition-all duration-200 ease-in -m-px py-4 px-0 items-center border border-solid border-slate-25 dark:border-slate-800 hover:border-woot-500 dark:hover:border-woot-500 hover:shadow-md hover:z-50 disabled:opacity-60"
|
||||
>
|
||||
<img :src="src" :alt="title" class="w-1/2 my-4 mx-auto" />
|
||||
<img :src="src" :alt="title" draggable="false" class="w-1/2 my-4 mx-auto" />
|
||||
<h3
|
||||
class="text-slate-800 dark:text-slate-100 text-base text-center capitalize"
|
||||
>
|
||||
|
||||
@@ -756,6 +756,7 @@ function toggleSelectAll(check) {
|
||||
}
|
||||
|
||||
useEmitter('fetch_conversation_stats', () => {
|
||||
if (hasAppliedFiltersOrActiveFolders.value) return;
|
||||
store.dispatch('conversationStats/get', conversationFilters.value);
|
||||
});
|
||||
|
||||
@@ -853,6 +854,8 @@ watch(conversationFilters, (newVal, oldVal) => {
|
||||
:has-active-folders="hasActiveFolders"
|
||||
:active-status="activeStatus"
|
||||
:is-on-expanded-layout="isOnExpandedLayout"
|
||||
:conversation-stats="conversationStats"
|
||||
:is-list-loading="chatListLoading && !conversationList.length"
|
||||
@add-folders="onClickOpenAddFoldersModal"
|
||||
@delete-folders="onClickOpenDeleteFoldersModal"
|
||||
@filters-modal="onToggleAdvanceFiltersModal"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
import { formatNumber } from '@chatwoot/utils';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
@@ -10,26 +11,13 @@ import SwitchLayout from 'dashboard/routes/dashboard/conversation/search/SwitchL
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
const props = defineProps({
|
||||
pageTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hasAppliedFilters: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
hasActiveFolders: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
activeStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isOnExpandedLayout: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
pageTitle: { type: String, required: true },
|
||||
hasAppliedFilters: { type: Boolean, required: true },
|
||||
hasActiveFolders: { type: Boolean, required: true },
|
||||
activeStatus: { type: String, required: true },
|
||||
isOnExpandedLayout: { type: Boolean, required: true },
|
||||
conversationStats: { type: Object, required: true },
|
||||
isListLoading: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -62,6 +50,9 @@ const showV4View = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const allCount = computed(() => props.conversationStats?.allCount || 0);
|
||||
const formattedAllCount = computed(() => formatNumber(allCount.value));
|
||||
|
||||
const toggleConversationLayout = () => {
|
||||
const { LAYOUT_TYPES } = wootConstants;
|
||||
const {
|
||||
@@ -92,6 +83,15 @@ const toggleConversationLayout = () => {
|
||||
>
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
<span
|
||||
v-if="
|
||||
allCount > 0 && hasAppliedFiltersOrActiveFolders && !isListLoading
|
||||
"
|
||||
class="px-2 py-1 my-0.5 mx-1 rounded-md capitalize bg-n-slate-3 text-xxs text-n-slate-12 shrink-0"
|
||||
:title="allCount"
|
||||
>
|
||||
{{ formattedAllCount }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
class="px-2 py-1 my-0.5 mx-1 rounded-md capitalize bg-n-slate-3 text-xxs text-n-slate-12 shrink-0"
|
||||
|
||||
@@ -21,7 +21,7 @@ const props = defineProps({
|
||||
const isRelaxed = computed(() => props.type === 'relaxed');
|
||||
const headerClass = computed(() =>
|
||||
isRelaxed.value
|
||||
? 'first:rounded-bl-lg first:rounded-tl-lg last:rounded-br-lg last:rounded-tr-lg'
|
||||
? 'ltr:first:rounded-bl-lg ltr:first:rounded-tl-lg ltr:last:rounded-br-lg ltr:last:rounded-tr-lg rtl:first:rounded-br-lg rtl:first:rounded-tr-lg rtl:last:rounded-bl-lg rtl:last:rounded-tl-lg'
|
||||
: ''
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -41,6 +41,10 @@ export default {
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'voice') {
|
||||
return this.enabledFeatures.channel_voice;
|
||||
}
|
||||
|
||||
return [
|
||||
'website',
|
||||
'twilio',
|
||||
@@ -50,6 +54,7 @@ export default {
|
||||
'telegram',
|
||||
'line',
|
||||
'instagram',
|
||||
'voice',
|
||||
].includes(key);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,8 +11,6 @@ import SLACardLabel from './components/SLACardLabel.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
|
||||
import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import Linear from './linear/index.vue';
|
||||
import { useInbox } from 'dashboard/composables/useInbox';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -36,12 +34,6 @@ const { isAWebWidgetInbox } = useInbox();
|
||||
|
||||
const currentChat = computed(() => store.getters.getSelectedChat);
|
||||
const accountId = computed(() => store.getters.getCurrentAccountId);
|
||||
const isFeatureEnabledonAccount = computed(
|
||||
() => store.getters['accounts/isFeatureEnabledonAccount']
|
||||
);
|
||||
const appIntegrations = computed(
|
||||
() => store.getters['integrations/getAppIntegrations']
|
||||
);
|
||||
|
||||
const chatMetadata = computed(() => props.chat.meta);
|
||||
|
||||
@@ -92,16 +84,6 @@ const hasMultipleInboxes = computed(
|
||||
);
|
||||
|
||||
const hasSlaPolicyId = computed(() => props.chat?.sla_policy_id);
|
||||
|
||||
const isLinearIntegrationEnabled = computed(() =>
|
||||
appIntegrations.value.find(
|
||||
integration => integration.id === 'linear' && !!integration.hooks.length
|
||||
)
|
||||
);
|
||||
|
||||
const isLinearFeatureEnabled = computed(() =>
|
||||
isFeatureEnabledonAccount.value(accountId.value, FEATURE_FLAGS.LINEAR)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -162,12 +144,6 @@ const isLinearFeatureEnabled = computed(() =>
|
||||
:parent-width="width"
|
||||
class="hidden md:flex"
|
||||
/>
|
||||
<Linear
|
||||
v-if="isLinearIntegrationEnabled && isLinearFeatureEnabled"
|
||||
:conversation-id="currentChat.id"
|
||||
:parent-width="width"
|
||||
class="hidden md:flex"
|
||||
/>
|
||||
<MoreActions :conversation-id="currentChat.id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -183,13 +183,18 @@ const createIssue = async () => {
|
||||
state_id: formState.stateId || undefined,
|
||||
priority: formState.priority || undefined,
|
||||
label_ids: formState.labelId ? [formState.labelId] : undefined,
|
||||
conversation_id: props.conversationId,
|
||||
};
|
||||
|
||||
try {
|
||||
isCreating.value = true;
|
||||
const response = await LinearAPI.createIssue(payload);
|
||||
const { id: issueId } = response.data;
|
||||
await LinearAPI.link_issue(props.conversationId, issueId, props.title);
|
||||
const { identifier: issueIdentifier } = response.data;
|
||||
await LinearAPI.link_issue(
|
||||
props.conversationId,
|
||||
issueIdentifier,
|
||||
props.title
|
||||
);
|
||||
useAlert(t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE_SUCCESS'));
|
||||
useTrack(LINEAR_EVENTS.CREATE_ISSUE);
|
||||
onClose();
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<script setup>
|
||||
import { format } from 'date-fns';
|
||||
import UserAvatarWithName from 'dashboard/components/widgets/UserAvatarWithName.vue';
|
||||
import IssueHeader from './IssueHeader.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
linkId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['unlinkIssue']);
|
||||
|
||||
const priorityMap = {
|
||||
1: 'Urgent',
|
||||
2: 'High',
|
||||
3: 'Medium',
|
||||
4: 'Low',
|
||||
};
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
const { createdAt } = props.issue;
|
||||
return format(new Date(createdAt), 'hh:mm a, MMM dd');
|
||||
});
|
||||
|
||||
const assignee = computed(() => {
|
||||
const assigneeDetails = props.issue.assignee;
|
||||
|
||||
if (!assigneeDetails) return null;
|
||||
const { name, avatarUrl } = assigneeDetails;
|
||||
|
||||
return {
|
||||
name,
|
||||
thumbnail: avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
const labels = computed(() => {
|
||||
return props.issue.labels?.nodes || [];
|
||||
});
|
||||
|
||||
const priorityLabel = computed(() => {
|
||||
return priorityMap[props.issue.priority];
|
||||
});
|
||||
|
||||
const unlinkIssue = () => {
|
||||
emit('unlinkIssue', props.linkId);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="absolute flex flex-col items-start bg-n-alpha-3 backdrop-blur-[100px] z-50 px-4 py-3 border border-solid border-n-container w-[384px] rounded-xl gap-4 max-h-96 overflow-auto"
|
||||
>
|
||||
<div class="flex flex-col w-full">
|
||||
<IssueHeader
|
||||
:identifier="issue.identifier"
|
||||
:link-id="linkId"
|
||||
:issue-url="issue.url"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
|
||||
<span class="mt-2 text-sm font-medium text-n-slate-12">
|
||||
{{ issue.title }}
|
||||
</span>
|
||||
<span
|
||||
v-if="issue.description"
|
||||
class="mt-1 text-sm text-n-slate-11 line-clamp-3"
|
||||
>
|
||||
{{ issue.description }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-row items-center h-6 gap-2">
|
||||
<UserAvatarWithName v-if="assignee" :user="assignee" class="py-1" />
|
||||
<div v-if="assignee" class="w-px h-3 bg-n-slate-4" />
|
||||
<div class="flex items-center gap-1 py-1">
|
||||
<fluent-icon
|
||||
icon="status"
|
||||
size="14"
|
||||
:style="{ color: issue.state.color }"
|
||||
/>
|
||||
<h6 class="text-xs text-n-slate-12">
|
||||
{{ issue.state.name }}
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="priorityLabel" class="w-px h-3 bg-n-slate-4" />
|
||||
<div v-if="priorityLabel" class="flex items-center gap-1 py-1">
|
||||
<fluent-icon
|
||||
:icon="`priority-${priorityLabel.toLowerCase()}`"
|
||||
size="14"
|
||||
view-box="0 0 12 12"
|
||||
/>
|
||||
<h6 class="text-xs text-n-slate-12">{{ priorityLabel }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="labels.length" class="flex flex-wrap items-center gap-1">
|
||||
<woot-label
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:title="label.name"
|
||||
:description="label.description"
|
||||
:color="label.color"
|
||||
variant="smooth"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-n-slate-11">
|
||||
{{
|
||||
$t('INTEGRATION_SETTINGS.LINEAR.ISSUE.CREATED_AT', {
|
||||
createdAt: formattedDate,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup>
|
||||
import { inject } from 'vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -15,8 +14,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['unlinkIssue']);
|
||||
|
||||
const isUnlinking = inject('isUnlinking');
|
||||
|
||||
const unlinkIssue = () => {
|
||||
emit('unlinkIssue');
|
||||
};
|
||||
@@ -27,36 +24,33 @@ const openIssue = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
class="flex items-center justify-center gap-1 h-[24px] px-2 py-1 border rounded-lg border-ash-200"
|
||||
class="flex items-center gap-2 px-2 py-1.5 border rounded-lg border-n-strong"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="linear"
|
||||
size="19"
|
||||
class="text-[#5E6AD2]"
|
||||
view-box="0 0 19 19"
|
||||
/>
|
||||
<span class="text-xs font-medium text-ash-900">{{ identifier }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<fluent-icon
|
||||
icon="linear"
|
||||
size="16"
|
||||
class="text-[#5E6AD2]"
|
||||
view-box="0 0 19 19"
|
||||
/>
|
||||
<span class="text-xs font-medium text-n-slate-12">
|
||||
{{ identifier }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="w-px h-3 text-n-weak bg-n-weak" />
|
||||
|
||||
<Button
|
||||
ghost
|
||||
link
|
||||
xs
|
||||
slate
|
||||
icon="i-lucide-unlink"
|
||||
class="!transition-none"
|
||||
:is-loading="isUnlinking"
|
||||
@click="unlinkIssue"
|
||||
/>
|
||||
<Button
|
||||
ghost
|
||||
xs
|
||||
slate
|
||||
class="!transition-none"
|
||||
icon="i-lucide-arrow-up-right"
|
||||
class="!size-4"
|
||||
@click="openIssue"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button ghost xs slate icon="i-lucide-unlink" @click="unlinkIssue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, watch } from 'vue';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useStoreGetters } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LinearAPI from 'dashboard/api/integrations/linear';
|
||||
import CreateOrLinkIssue from './CreateOrLinkIssue.vue';
|
||||
import LinearIssueItem from './LinearIssueItem.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
import { LINEAR_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const getters = useStoreGetters();
|
||||
|
||||
const linkedIssues = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const shouldShowCreateModal = ref(false);
|
||||
|
||||
const currentAccountId = getters.getCurrentAccountId;
|
||||
|
||||
const conversation = computed(
|
||||
() => getters.getConversationById.value(props.conversationId) || {}
|
||||
);
|
||||
|
||||
const hasIssues = computed(() => linkedIssues.value.length > 0);
|
||||
|
||||
const loadLinkedIssues = async () => {
|
||||
isLoading.value = true;
|
||||
linkedIssues.value = [];
|
||||
try {
|
||||
const response = await LinearAPI.getLinkedIssue(props.conversationId);
|
||||
linkedIssues.value = response.data || [];
|
||||
} catch (error) {
|
||||
// Silent fail - not critical for UX
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const unlinkIssue = async (linkId, issueIdentifier) => {
|
||||
try {
|
||||
await LinearAPI.unlinkIssue(linkId, issueIdentifier, props.conversationId);
|
||||
useTrack(LINEAR_EVENTS.UNLINK_ISSUE);
|
||||
linkedIssues.value = linkedIssues.value.filter(
|
||||
issue => issue.id !== linkId
|
||||
);
|
||||
useAlert(t('INTEGRATION_SETTINGS.LINEAR.UNLINK.SUCCESS'));
|
||||
} catch (error) {
|
||||
const errorMessage = parseLinearAPIErrorResponse(
|
||||
error,
|
||||
t('INTEGRATION_SETTINGS.LINEAR.UNLINK.ERROR')
|
||||
);
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
shouldShowCreateModal.value = true;
|
||||
};
|
||||
|
||||
const closeCreateModal = () => {
|
||||
shouldShowCreateModal.value = false;
|
||||
loadLinkedIssues();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.conversationId,
|
||||
() => {
|
||||
loadLinkedIssues();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
loadLinkedIssues();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="px-4 pt-3 pb-2">
|
||||
<NextButton
|
||||
ghost
|
||||
xs
|
||||
icon="i-lucide-plus"
|
||||
:label="$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK_BUTTON')"
|
||||
@click="openCreateModal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="flex justify-center p-8">
|
||||
<Spinner />
|
||||
</div>
|
||||
|
||||
<div v-else-if="!hasIssues" class="flex justify-center p-4">
|
||||
<p class="text-sm text-n-slate-11">
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.NO_LINKED_ISSUES') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="max-h-[300px] overflow-y-auto">
|
||||
<LinearIssueItem
|
||||
v-for="linkedIssue in linkedIssues"
|
||||
:key="linkedIssue.id"
|
||||
class="px-4 pt-3 pb-4 border-b border-n-weak last:border-b-0"
|
||||
:linked-issue="linkedIssue"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<woot-modal
|
||||
v-model:show="shouldShowCreateModal"
|
||||
:on-close="closeCreateModal"
|
||||
:close-on-backdrop-click="false"
|
||||
class="!items-start [&>div]:!top-12 [&>div]:sticky"
|
||||
>
|
||||
<CreateOrLinkIssue
|
||||
:conversation="conversation"
|
||||
:account-id="currentAccountId"
|
||||
@close="closeCreateModal"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,113 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
||||
import CardPriorityIcon from 'dashboard/components-next/Conversation/ConversationCard/CardPriorityIcon.vue';
|
||||
import IssueHeader from './IssueHeader.vue';
|
||||
|
||||
const props = defineProps({
|
||||
linkedIssue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['unlinkIssue']);
|
||||
|
||||
const { linkedIssue } = props;
|
||||
|
||||
const priorityMap = {
|
||||
1: 'Urgent',
|
||||
2: 'High',
|
||||
3: 'Medium',
|
||||
4: 'Low',
|
||||
};
|
||||
|
||||
const issue = computed(() => linkedIssue.issue);
|
||||
|
||||
const assignee = computed(() => {
|
||||
const assigneeDetails = issue.value.assignee;
|
||||
if (!assigneeDetails) return null;
|
||||
return {
|
||||
name: assigneeDetails.name,
|
||||
thumbnail: assigneeDetails.avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
const labels = computed(() => issue.value.labels?.nodes || []);
|
||||
|
||||
const priorityLabel = computed(() => priorityMap[issue.value.priority]);
|
||||
|
||||
const unlinkIssue = () => {
|
||||
emit('unlinkIssue', linkedIssue.id, linkedIssue.issue.identifier);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col w-full">
|
||||
<IssueHeader
|
||||
:identifier="issue.identifier"
|
||||
:link-id="linkedIssue.id"
|
||||
:issue-url="issue.url"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
|
||||
<h3 class="mt-2 text-sm font-medium text-n-slate-12">
|
||||
{{ issue.title }}
|
||||
</h3>
|
||||
|
||||
<p
|
||||
v-if="issue.description"
|
||||
class="mt-1 text-sm text-n-slate-11 line-clamp-3"
|
||||
>
|
||||
{{ issue.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="assignee" class="flex items-center gap-1.5">
|
||||
<Avatar :src="assignee.thumbnail" :name="assignee.name" :size="16" />
|
||||
<span class="text-xs capitalize truncate text-n-slate-12">
|
||||
{{ assignee.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="assignee" class="w-px h-3 bg-n-slate-4" />
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<Icon
|
||||
icon="i-lucide-activity"
|
||||
class="size-4"
|
||||
:style="{ color: issue.state?.color }"
|
||||
/>
|
||||
<span class="text-xs text-n-slate-12">
|
||||
{{ issue.state?.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="priorityLabel" class="w-px h-3 bg-n-slate-4" />
|
||||
|
||||
<div v-if="priorityLabel" class="flex items-center gap-1.5">
|
||||
<CardPriorityIcon :priority="priorityLabel.toLowerCase()" />
|
||||
<span class="text-xs text-n-slate-12">
|
||||
{{ priorityLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="labels.length" class="flex flex-wrap">
|
||||
<woot-label
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:title="label.name"
|
||||
:description="label.description"
|
||||
:color="label.color"
|
||||
variant="smooth"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useStoreGetters } from 'dashboard/composables/store';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
|
||||
const { isAdmin } = useAdmin();
|
||||
const getters = useStoreGetters();
|
||||
const accountId = getters.getCurrentAccountId;
|
||||
|
||||
const integrationId = 'linear';
|
||||
|
||||
const actionURL = computed(() =>
|
||||
frontendURL(
|
||||
`accounts/${accountId.value}/settings/integrations/${integrationId}`
|
||||
)
|
||||
);
|
||||
|
||||
const openLinearAccount = () => {
|
||||
window.open(actionURL.value, '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col p-3">
|
||||
<div class="w-12 h-12 mb-3">
|
||||
<img
|
||||
:src="`/dashboard/images/integrations/${integrationId}.png`"
|
||||
class="object-contain w-full h-full border rounded-md shadow-sm border-n-weak dark:hidden dark:bg-n-alpha-2"
|
||||
/>
|
||||
<img
|
||||
:src="`/dashboard/images/integrations/${integrationId}-dark.png`"
|
||||
class="hidden object-contain w-full h-full border rounded-md shadow-sm border-n-weak dark:block"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 mb-4">
|
||||
<h3 class="mb-1.5 text-sm font-medium text-n-slate-12">
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.CTA.TITLE') }}
|
||||
</h3>
|
||||
<p v-if="isAdmin" class="text-sm text-n-slate-11">
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.CTA.DESCRIPTION') }}
|
||||
</p>
|
||||
<p v-else class="text-sm text-n-slate-11">
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.CTA.AGENT_DESCRIPTION') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<NextButton v-if="isAdmin" faded slate @click="openLinearAccount">
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.CTA.BUTTON_TEXT') }}
|
||||
</NextButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -63,7 +63,7 @@ const onSearch = async value => {
|
||||
isFetching.value = true;
|
||||
const response = await LinearAPI.searchIssues(value);
|
||||
issues.value = response.data.map(issue => ({
|
||||
id: issue.id,
|
||||
id: issue.identifier,
|
||||
name: `${issue.identifier} ${issue.title}`,
|
||||
icon: 'status',
|
||||
iconColor: issue.state.color,
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, watch, defineOptions, provide } from 'vue';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useStoreGetters } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LinearAPI from 'dashboard/api/integrations/linear';
|
||||
import CreateOrLinkIssue from './CreateOrLinkIssue.vue';
|
||||
import Issue from './Issue.vue';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
import { LINEAR_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
parentWidth: {
|
||||
type: Number,
|
||||
default: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
defineOptions({
|
||||
name: 'Linear',
|
||||
});
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const { t } = useI18n();
|
||||
|
||||
const linkedIssue = ref(null);
|
||||
const shouldShow = ref(false);
|
||||
const shouldShowPopup = ref(false);
|
||||
const isUnlinking = ref(false);
|
||||
|
||||
provide('isUnlinking', isUnlinking);
|
||||
|
||||
const currentAccountId = getters.getCurrentAccountId;
|
||||
|
||||
const conversation = computed(() =>
|
||||
getters.getConversationById.value(props.conversationId)
|
||||
);
|
||||
|
||||
const tooltipText = computed(() => {
|
||||
return linkedIssue.value === null
|
||||
? t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK_BUTTON')
|
||||
: null;
|
||||
});
|
||||
|
||||
const loadLinkedIssue = async () => {
|
||||
linkedIssue.value = null;
|
||||
try {
|
||||
const response = await LinearAPI.getLinkedIssue(props.conversationId);
|
||||
const issues = response.data;
|
||||
linkedIssue.value = issues && issues.length ? issues[0] : null;
|
||||
} catch (error) {
|
||||
// We don't want to show an error message here, as it's not critical. When someone clicks on the Linear icon, we can inform them that the integration is disabled.
|
||||
}
|
||||
};
|
||||
|
||||
const unlinkIssue = async linkId => {
|
||||
try {
|
||||
isUnlinking.value = true;
|
||||
await LinearAPI.unlinkIssue(linkId);
|
||||
useTrack(LINEAR_EVENTS.UNLINK_ISSUE);
|
||||
linkedIssue.value = null;
|
||||
useAlert(t('INTEGRATION_SETTINGS.LINEAR.UNLINK.SUCCESS'));
|
||||
} catch (error) {
|
||||
const errorMessage = parseLinearAPIErrorResponse(
|
||||
error,
|
||||
t('INTEGRATION_SETTINGS.LINEAR.UNLINK.ERROR')
|
||||
);
|
||||
useAlert(errorMessage);
|
||||
} finally {
|
||||
isUnlinking.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const shouldShowIssueIdentifier = computed(() => {
|
||||
if (!linkedIssue.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return props.parentWidth > 600;
|
||||
});
|
||||
|
||||
const openIssue = () => {
|
||||
if (!linkedIssue.value) shouldShowPopup.value = true;
|
||||
shouldShow.value = true;
|
||||
};
|
||||
|
||||
const closePopup = () => {
|
||||
shouldShowPopup.value = false;
|
||||
loadLinkedIssue();
|
||||
};
|
||||
|
||||
const closeIssue = () => {
|
||||
shouldShow.value = false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.conversationId,
|
||||
() => {
|
||||
loadLinkedIssue();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
loadLinkedIssue();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative after:content-[''] after:h-5 after:bg-transparent after:top-5 after:w-full after:block after:absolute after:z-0"
|
||||
:class="{ group: linkedIssue }"
|
||||
>
|
||||
<Button
|
||||
v-on-clickaway="closeIssue"
|
||||
v-tooltip="tooltipText"
|
||||
sm
|
||||
ghost
|
||||
slate
|
||||
class="!gap-1 group-hover:bg-n-alpha-2"
|
||||
@click="openIssue"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="linear"
|
||||
size="19"
|
||||
class="text-[#5E6AD2] flex-shrink-0"
|
||||
view-box="0 0 19 19"
|
||||
/>
|
||||
<span
|
||||
v-if="shouldShowIssueIdentifier"
|
||||
class="text-xs font-medium text-n-slate-11"
|
||||
>
|
||||
{{ linkedIssue.issue.identifier }}
|
||||
</span>
|
||||
</Button>
|
||||
<Issue
|
||||
v-if="linkedIssue"
|
||||
:issue="linkedIssue.issue"
|
||||
:link-id="linkedIssue.id"
|
||||
class="absolute start-0 xl:start-auto xl:end-0 top-9 invisible group-hover:visible"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
<woot-modal
|
||||
v-model:show="shouldShowPopup"
|
||||
:on-close="closePopup"
|
||||
:close-on-backdrop-click="false"
|
||||
class="!items-start [&>div]:!top-12 [&>div]:sticky"
|
||||
>
|
||||
<CreateOrLinkIssue
|
||||
:conversation="conversation"
|
||||
:account-id="currentAccountId"
|
||||
@close="closePopup"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -125,6 +125,10 @@ export const useInbox = () => {
|
||||
return channelType.value === INBOX_TYPES.INSTAGRAM;
|
||||
});
|
||||
|
||||
const isAVoiceChannel = computed(() => {
|
||||
return channelType.value === INBOX_TYPES.VOICE;
|
||||
});
|
||||
|
||||
return {
|
||||
inbox,
|
||||
isAFacebookInbox,
|
||||
@@ -142,5 +146,6 @@ export const useInbox = () => {
|
||||
is360DialogWhatsAppChannel,
|
||||
isAnEmailChannel,
|
||||
isAnInstagramChannel,
|
||||
isAVoiceChannel,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = Object.freeze([
|
||||
{ name: 'contact_notes' },
|
||||
{ name: 'previous_conversation' },
|
||||
{ name: 'conversation_participants' },
|
||||
{ name: 'linear_issues' },
|
||||
{ name: 'shopify_orders' },
|
||||
]);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export const INBOX_TYPES = {
|
||||
LINE: 'Channel::Line',
|
||||
SMS: 'Channel::Sms',
|
||||
INSTAGRAM: 'Channel::Instagram',
|
||||
VOICE: 'Channel::Voice',
|
||||
};
|
||||
|
||||
const INBOX_ICON_MAP_FILL = {
|
||||
@@ -22,6 +23,7 @@ const INBOX_ICON_MAP_FILL = {
|
||||
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-fill',
|
||||
[INBOX_TYPES.LINE]: 'i-ri-line-fill',
|
||||
[INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-fill',
|
||||
[INBOX_TYPES.VOICE]: 'i-ri-phone-fill',
|
||||
};
|
||||
|
||||
const DEFAULT_ICON_FILL = 'i-ri-chat-1-fill';
|
||||
@@ -36,6 +38,7 @@ const INBOX_ICON_MAP_LINE = {
|
||||
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-line',
|
||||
[INBOX_TYPES.LINE]: 'i-ri-line-line',
|
||||
[INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-line',
|
||||
[INBOX_TYPES.VOICE]: 'i-ri-phone-line',
|
||||
};
|
||||
|
||||
const DEFAULT_ICON_LINE = 'i-ri-chat-1-line';
|
||||
@@ -47,6 +50,7 @@ export const getInboxSource = (type, phoneNumber, inbox) => {
|
||||
|
||||
case INBOX_TYPES.TWILIO:
|
||||
case INBOX_TYPES.WHATSAPP:
|
||||
case INBOX_TYPES.VOICE:
|
||||
return phoneNumber || '';
|
||||
|
||||
case INBOX_TYPES.EMAIL:
|
||||
@@ -85,6 +89,9 @@ export const getReadableInboxByType = (type, phoneNumber) => {
|
||||
case INBOX_TYPES.LINE:
|
||||
return 'line';
|
||||
|
||||
case INBOX_TYPES.VOICE:
|
||||
return 'voice';
|
||||
|
||||
default:
|
||||
return 'chat';
|
||||
}
|
||||
@@ -124,6 +131,9 @@ export const getInboxClassByType = (type, phoneNumber) => {
|
||||
case INBOX_TYPES.INSTAGRAM:
|
||||
return 'brand-instagram';
|
||||
|
||||
case INBOX_TYPES.VOICE:
|
||||
return 'phone';
|
||||
|
||||
default:
|
||||
return 'chat';
|
||||
}
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
"ERROR_MESSAGE": "Could not update bot. Please try again."
|
||||
}
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"DESCRIPTION": "Copy the access token and save it securely",
|
||||
"COPY_SUCCESSFUL": "Access token copied to clipboard",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"FORM": {
|
||||
"AVATAR": {
|
||||
"LABEL": "Bot avatar"
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "{agentName} updated the account configuration (#{id})"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"DELETE": "{agentName} deleted conversation #{id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@
|
||||
"HEADER": {
|
||||
"TITLE": "Contacts",
|
||||
"SEARCH_TITLE": "Search contacts",
|
||||
"ACTIVE_TITLE": "Active contacts",
|
||||
"SEARCH_PLACEHOLDER": "Search...",
|
||||
"MESSAGE_BUTTON": "Message",
|
||||
"SEND_MESSAGE": "Send message",
|
||||
@@ -457,6 +458,10 @@
|
||||
"PLACEHOLDER": "Add Twitter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE_CONTACT": {
|
||||
"MESSAGE": "This action is permanent and irreversible.",
|
||||
"BUTTON": "Delete now"
|
||||
}
|
||||
},
|
||||
"DETAILS": {
|
||||
@@ -466,7 +471,7 @@
|
||||
"DELETE_CONTACT": "Delete contact",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Confirm Deletion",
|
||||
"DESCRIPTION": "Are you sure you want to delete this {contactName} contact?",
|
||||
"DESCRIPTION": "Are you sure you want to delete this contact?",
|
||||
"CONFIRM": "Yes, Delete",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Contact deleted successfully",
|
||||
@@ -555,7 +560,8 @@
|
||||
"SUBTITLE": "Start adding new contacts by clicking on the button below",
|
||||
"BUTTON_LABEL": "Add contact",
|
||||
"SEARCH_EMPTY_STATE_TITLE": "No contacts matches your search 🔍",
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋"
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋",
|
||||
"ACTIVE_EMPTY_STATE_TITLE": "No contacts are active at the moment 🌙"
|
||||
}
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"RESOLVE_ACTION": "Resolve",
|
||||
"REOPEN_ACTION": "Reopen",
|
||||
"OPEN_ACTION": "Open",
|
||||
"MORE_ACTIONS": "More actions",
|
||||
"OPEN": "More",
|
||||
"CLOSE": "Close",
|
||||
"DETAILS": "details",
|
||||
@@ -117,6 +118,11 @@
|
||||
"FAILED": "Couldn't change priority. Please try again."
|
||||
}
|
||||
},
|
||||
"DELETE_CONVERSATION": {
|
||||
"TITLE": "Delete conversation #{conversationId}",
|
||||
"DESCRIPTION": "Are you sure you want to delete this conversation?",
|
||||
"CONFIRM": "Delete"
|
||||
},
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "Mark as pending",
|
||||
"RESOLVED": "Mark as resolved",
|
||||
@@ -133,6 +139,7 @@
|
||||
"ASSIGN_LABEL": "Assign label",
|
||||
"AGENTS_LOADING": "Loading agents...",
|
||||
"ASSIGN_TEAM": "Assign team",
|
||||
"DELETE": "Delete conversation",
|
||||
"API": {
|
||||
"AGENT_ASSIGNMENT": {
|
||||
"SUCCESFUL": "Conversation id {conversationId} assigned to \"{agentName}\"",
|
||||
@@ -207,6 +214,8 @@
|
||||
"ASSIGN_LABEL_SUCCESFUL": "Label assigned successfully",
|
||||
"ASSIGN_LABEL_FAILED": "Label assignment failed",
|
||||
"CHANGE_TEAM": "Conversation team changed",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Conversation deleted successfully",
|
||||
"FAIL_DELETE_CONVERSATION": "Couldn't delete conversation! Try again",
|
||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB attachment limit",
|
||||
"MESSAGE_ERROR": "Unable to send this message, please try again later",
|
||||
"SENT_BY": "Sent by:",
|
||||
@@ -299,6 +308,7 @@
|
||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||
"PREVIOUS_CONVERSATION": "Previous Conversations",
|
||||
"MACROS": "Macros",
|
||||
"LINEAR_ISSUES": "Linked Linear Issues",
|
||||
"SHOPIFY_ORDERS": "Shopify Orders"
|
||||
},
|
||||
"SHOPIFY": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"PHONE_INPUT": {
|
||||
"PLACEHOLDER": "Search",
|
||||
"EMPTY_STATE": "No results found"
|
||||
}
|
||||
},
|
||||
"CLOSE": "Close"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,31 @@
|
||||
},
|
||||
"AUTO_RESOLVE": {
|
||||
"TITLE": "Auto-resolve conversations",
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period. Set the duration and customize the message to the user below."
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period of inactivity.",
|
||||
"DURATION": {
|
||||
"LABEL": "Inactivity duration",
|
||||
"HELP": "Time period of inactivity after which conversation is auto-resolved",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Auto resolve duration should be between 10 minutes and 999 days",
|
||||
"API": {
|
||||
"SUCCESS": "Auto resolve settings updated successfully",
|
||||
"ERROR": "Failed to update auto resolve settings"
|
||||
}
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Custom auto-resolution message",
|
||||
"PLACEHOLDER": "Conversation was marked resolved by system due to 15 days of inactivity",
|
||||
"HELP": "Message sent to the customer after conversation is auto-resolved"
|
||||
},
|
||||
"PREFERENCES": "Preferences",
|
||||
"LABEL": {
|
||||
"LABEL": "Add label after auto-resolution",
|
||||
"PLACEHOLDER": "Select a label"
|
||||
},
|
||||
"IGNORE_WAITING": {
|
||||
"LABEL": "Skip conversations waiting for agent’s reply"
|
||||
},
|
||||
"UPDATE_BUTTON": "Save Changes"
|
||||
},
|
||||
"NAME": {
|
||||
"LABEL": "Account name",
|
||||
@@ -70,7 +94,15 @@
|
||||
},
|
||||
"AUTO_RESOLVE_IGNORE_WAITING": {
|
||||
"LABEL": "Exclude unattended conversations",
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent’s reply."
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent's reply."
|
||||
},
|
||||
"AUDIO_TRANSCRIPTION": {
|
||||
"TITLE": "Transcribe Audio Messages",
|
||||
"NOTE": "Automatically transcribe audio messages in conversations. Generate a text transcript whenever an audio message is sent or received, and display it alongside the message.",
|
||||
"API": {
|
||||
"SUCCESS": "Audio transcription setting updated successfully",
|
||||
"ERROR": "Failed to update audio transcription setting"
|
||||
}
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Inactivity duration for resolution",
|
||||
|
||||
@@ -318,11 +318,18 @@
|
||||
"SUCCESS": "Issue unlinked successfully",
|
||||
"ERROR": "There was an error unlinking the issue, please try again"
|
||||
},
|
||||
"NO_LINKED_ISSUES": "No linked issues found",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Yes, delete",
|
||||
"CANCEL": "Cancel"
|
||||
},
|
||||
"CTA": {
|
||||
"TITLE": "Connect to Linear",
|
||||
"AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
|
||||
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
|
||||
"BUTTON_TEXT": "Connect Linear workspace"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -330,12 +337,17 @@
|
||||
"NAME": "Captain",
|
||||
"HEADER_KNOW_MORE": "Know more",
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
"PANEL_TITLE": "Get started with Copilot",
|
||||
"KICK_OFF_MESSAGE": "Need a quick summary, want to check past conversations, or draft a better reply? Copilot’s here to speed things up.",
|
||||
"SEND_MESSAGE": "Send message...",
|
||||
"EMPTY_MESSAGE": "There was an error generating the response. Please try again.",
|
||||
"LOADER": "Captain is thinking",
|
||||
"YOU": "You",
|
||||
"USE": "Use this",
|
||||
"RESET": "Reset",
|
||||
"SHOW_STEPS": "Show steps",
|
||||
"SELECT_ASSISTANT": "Select Assistant",
|
||||
"PROMPTS": {
|
||||
"SUMMARIZE": {
|
||||
@@ -349,6 +361,14 @@
|
||||
"RATE": {
|
||||
"LABEL": "Rate this conversation",
|
||||
"CONTENT": "Review the conversation to see how well it meets the customer's needs. Share a rating out of 5 based on tone, clarity, and effectiveness."
|
||||
},
|
||||
"HIGH_PRIORITY": {
|
||||
"LABEL": "High priority conversations",
|
||||
"CONTENT": "Give me a summary of all high priority open conversations. Include the conversation ID, customer name (if available), last message content, and assigned agent. Group by status if relevant."
|
||||
},
|
||||
"LIST_CONTACTS": {
|
||||
"LABEL": "List contacts",
|
||||
"CONTENT": "Show me the list of top 10 contacts. Include name, email or phone number (if available), last seen time, tags (if any)."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,7 +388,6 @@
|
||||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||
},
|
||||
@@ -383,6 +402,7 @@
|
||||
},
|
||||
"ASSISTANTS": {
|
||||
"HEADER": "Assistants",
|
||||
"NO_ASSISTANTS_AVAILABLE": "There are no assistants available in your account.",
|
||||
"ADD_NEW": "Create a new assistant",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure to delete the assistant?",
|
||||
@@ -411,6 +431,10 @@
|
||||
"PLACEHOLDER": "Enter assistant name",
|
||||
"ERROR": "The name is required"
|
||||
},
|
||||
"TEMPERATURE": {
|
||||
"LABEL": "Response Temperature",
|
||||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter assistant description",
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "Labels Overview",
|
||||
"DESCRIPTION": "Track label performance with key metrics including conversations, response times, resolution times, and resolved cases. Click a label name for detailed insights.",
|
||||
"LOADING_CHART": "Loading chart data...",
|
||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "Download label reports",
|
||||
@@ -559,6 +560,7 @@
|
||||
"INBOX": "Inbox",
|
||||
"AGENT": "Agent",
|
||||
"TEAM": "Team",
|
||||
"LABEL": "Label",
|
||||
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
|
||||
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"ALL": "All",
|
||||
"CONTACTS": "Contacts",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "Contacts",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
|
||||
@@ -76,7 +76,12 @@
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"NOTE": "This token can be used if you are building an API based integration",
|
||||
"COPY": "Copy"
|
||||
"COPY": "Copy",
|
||||
"RESET": "Reset",
|
||||
"CONFIRM_RESET": "Are you sure?",
|
||||
"CONFIRM_HINT": "Click again to confirm",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Alerts",
|
||||
@@ -185,7 +190,8 @@
|
||||
"OFFLINE": "Offline"
|
||||
},
|
||||
"SET_AVAILABILITY_SUCCESS": "Availability has been set successfully",
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again"
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again",
|
||||
"IMPERSONATING_ERROR": "Cannot change availability while impersonating a user"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Your email address",
|
||||
@@ -280,6 +286,7 @@
|
||||
"REPORTS": "Reports",
|
||||
"SETTINGS": "Settings",
|
||||
"CONTACTS": "Contacts",
|
||||
"ACTIVE": "Active",
|
||||
"CAPTAIN": "Captain",
|
||||
"CAPTAIN_ASSISTANTS": "Assistants",
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
"ERROR_MESSAGE": "تعذر تحديث الروبوت. يرجى المحاولة مرة أخرى."
|
||||
}
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "رمز المصادقة",
|
||||
"DESCRIPTION": "Copy the access token and save it securely",
|
||||
"COPY_SUCCESSFUL": "Access token copied to clipboard",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"FORM": {
|
||||
"AVATAR": {
|
||||
"LABEL": "Bot avatar"
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "{agentName} قام بتحديث إعدادات الحساب (##{id})"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"DELETE": "{agentName} deleted conversation #{id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@
|
||||
"HEADER": {
|
||||
"TITLE": "جهات الاتصال",
|
||||
"SEARCH_TITLE": "Search contacts",
|
||||
"ACTIVE_TITLE": "Active contacts",
|
||||
"SEARCH_PLACEHOLDER": "Search...",
|
||||
"MESSAGE_BUTTON": "رسالة",
|
||||
"SEND_MESSAGE": "إرسال الرسالة",
|
||||
@@ -457,6 +458,10 @@
|
||||
"PLACEHOLDER": "Add Twitter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE_CONTACT": {
|
||||
"MESSAGE": "This action is permanent and irreversible.",
|
||||
"BUTTON": "Delete now"
|
||||
}
|
||||
},
|
||||
"DETAILS": {
|
||||
@@ -466,7 +471,7 @@
|
||||
"DELETE_CONTACT": "حذف جهة الاتصال",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "تأكيد الحذف",
|
||||
"DESCRIPTION": "Are you sure you want to delete this {contactName} contact?",
|
||||
"DESCRIPTION": "Are you sure you want to delete this contact?",
|
||||
"CONFIRM": "نعم، احذف",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "تم حذف جهة الاتصال بنجاح",
|
||||
@@ -555,7 +560,8 @@
|
||||
"SUBTITLE": "Start adding new contacts by clicking on the button below",
|
||||
"BUTTON_LABEL": "Add contact",
|
||||
"SEARCH_EMPTY_STATE_TITLE": "لا توجد جهات اتصال تطابق بحثك 🔍",
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋"
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋",
|
||||
"ACTIVE_EMPTY_STATE_TITLE": "No contacts are active at the moment 🌙"
|
||||
}
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"RESOLVE_ACTION": "حل المحادثة",
|
||||
"REOPEN_ACTION": "إعادة فتح",
|
||||
"OPEN_ACTION": "فتح",
|
||||
"MORE_ACTIONS": "More actions",
|
||||
"OPEN": "المزيد",
|
||||
"CLOSE": "أغلق",
|
||||
"DETAILS": "التفاصيل",
|
||||
@@ -117,6 +118,11 @@
|
||||
"FAILED": "تعذر تغيير الأولوية، الرجاء المحاولة مرة أخرى."
|
||||
}
|
||||
},
|
||||
"DELETE_CONVERSATION": {
|
||||
"TITLE": "Delete conversation #{conversationId}",
|
||||
"DESCRIPTION": "Are you sure you want to delete this conversation?",
|
||||
"CONFIRM": "حذف"
|
||||
},
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "تحديد كمعلق",
|
||||
"RESOLVED": "تحديد كمحلولة",
|
||||
@@ -133,6 +139,7 @@
|
||||
"ASSIGN_LABEL": "إضافة وسم",
|
||||
"AGENTS_LOADING": "جاري جلب الوكلاء...",
|
||||
"ASSIGN_TEAM": "تعيين فريق",
|
||||
"DELETE": "Delete conversation",
|
||||
"API": {
|
||||
"AGENT_ASSIGNMENT": {
|
||||
"SUCCESFUL": "معرف المحادثة {conversationId} تم تعيينه لـ \"{agentName}\"",
|
||||
@@ -207,6 +214,8 @@
|
||||
"ASSIGN_LABEL_SUCCESFUL": "تم تعيين الوسم بنجاح",
|
||||
"ASSIGN_LABEL_FAILED": "فشل تعيين الوسم",
|
||||
"CHANGE_TEAM": "تم تغيير فريق المحادثة",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Conversation deleted successfully",
|
||||
"FAIL_DELETE_CONVERSATION": "Couldn't delete conversation! Try again",
|
||||
"FILE_SIZE_LIMIT": "حجم الملف يتجاوز حد الاقصى وهو {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE}",
|
||||
"MESSAGE_ERROR": "غير قادر على إرسال هذه الرسالة، الرجاء المحاولة مرة أخرى لاحقاً",
|
||||
"SENT_BY": "أرسلت بواسطة:",
|
||||
@@ -299,6 +308,7 @@
|
||||
"CONTACT_ATTRIBUTES": "سمات جهة الاتصال",
|
||||
"PREVIOUS_CONVERSATION": "المحادثات السابقة",
|
||||
"MACROS": "ماكروس",
|
||||
"LINEAR_ISSUES": "Linked Linear Issues",
|
||||
"SHOPIFY_ORDERS": "Shopify Orders"
|
||||
},
|
||||
"SHOPIFY": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"PHONE_INPUT": {
|
||||
"PLACEHOLDER": "بحث",
|
||||
"EMPTY_STATE": "لم يتم العثور على النتائج"
|
||||
}
|
||||
},
|
||||
"CLOSE": "أغلق"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,31 @@
|
||||
},
|
||||
"AUTO_RESOLVE": {
|
||||
"TITLE": "Auto-resolve conversations",
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period. Set the duration and customize the message to the user below."
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period of inactivity.",
|
||||
"DURATION": {
|
||||
"LABEL": "Inactivity duration",
|
||||
"HELP": "Time period of inactivity after which conversation is auto-resolved",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Auto resolve duration should be between 10 minutes and 999 days",
|
||||
"API": {
|
||||
"SUCCESS": "Auto resolve settings updated successfully",
|
||||
"ERROR": "Failed to update auto resolve settings"
|
||||
}
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Custom auto-resolution message",
|
||||
"PLACEHOLDER": "Conversation was marked resolved by system due to 15 days of inactivity",
|
||||
"HELP": "Message sent to the customer after conversation is auto-resolved"
|
||||
},
|
||||
"PREFERENCES": "التفضيلات",
|
||||
"LABEL": {
|
||||
"LABEL": "Add label after auto-resolution",
|
||||
"PLACEHOLDER": "Select a label"
|
||||
},
|
||||
"IGNORE_WAITING": {
|
||||
"LABEL": "Skip conversations waiting for agent’s reply"
|
||||
},
|
||||
"UPDATE_BUTTON": "Save Changes"
|
||||
},
|
||||
"NAME": {
|
||||
"LABEL": "اسم الحساب",
|
||||
@@ -70,7 +94,15 @@
|
||||
},
|
||||
"AUTO_RESOLVE_IGNORE_WAITING": {
|
||||
"LABEL": "Exclude unattended conversations",
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent’s reply."
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent's reply."
|
||||
},
|
||||
"AUDIO_TRANSCRIPTION": {
|
||||
"TITLE": "Transcribe Audio Messages",
|
||||
"NOTE": "Automatically transcribe audio messages in conversations. Generate a text transcript whenever an audio message is sent or received, and display it alongside the message.",
|
||||
"API": {
|
||||
"SUCCESS": "Audio transcription setting updated successfully",
|
||||
"ERROR": "Failed to update audio transcription setting"
|
||||
}
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Inactivity duration for resolution",
|
||||
|
||||
@@ -318,11 +318,18 @@
|
||||
"SUCCESS": "تم إلغاء ربط المشكلة بنجاح",
|
||||
"ERROR": "حدث خطأ أثناء إلغاء ربط المشكلة، الرجاء المحاولة مرة أخرى"
|
||||
},
|
||||
"NO_LINKED_ISSUES": "No linked issues found",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "نعم، احذف",
|
||||
"CANCEL": "إلغاء"
|
||||
},
|
||||
"CTA": {
|
||||
"TITLE": "Connect to Linear",
|
||||
"AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
|
||||
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
|
||||
"BUTTON_TEXT": "Connect Linear workspace"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -330,12 +337,17 @@
|
||||
"NAME": "قائد",
|
||||
"HEADER_KNOW_MORE": "Know more",
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
"PANEL_TITLE": "Get started with Copilot",
|
||||
"KICK_OFF_MESSAGE": "Need a quick summary, want to check past conversations, or draft a better reply? Copilot’s here to speed things up.",
|
||||
"SEND_MESSAGE": "إرسال الرسالة...",
|
||||
"EMPTY_MESSAGE": "There was an error generating the response. Please try again.",
|
||||
"LOADER": "Captain is thinking",
|
||||
"YOU": "أنت",
|
||||
"USE": "Use this",
|
||||
"RESET": "Reset",
|
||||
"SHOW_STEPS": "Show steps",
|
||||
"SELECT_ASSISTANT": "Select Assistant",
|
||||
"PROMPTS": {
|
||||
"SUMMARIZE": {
|
||||
@@ -349,6 +361,14 @@
|
||||
"RATE": {
|
||||
"LABEL": "Rate this conversation",
|
||||
"CONTENT": "Review the conversation to see how well it meets the customer's needs. Share a rating out of 5 based on tone, clarity, and effectiveness."
|
||||
},
|
||||
"HIGH_PRIORITY": {
|
||||
"LABEL": "High priority conversations",
|
||||
"CONTENT": "Give me a summary of all high priority open conversations. Include the conversation ID, customer name (if available), last message content, and assigned agent. Group by status if relevant."
|
||||
},
|
||||
"LIST_CONTACTS": {
|
||||
"LABEL": "List contacts",
|
||||
"CONTENT": "Show me the list of top 10 contacts. Include name, email or phone number (if available), last seen time, tags (if any)."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,7 +388,6 @@
|
||||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||
},
|
||||
@@ -383,6 +402,7 @@
|
||||
},
|
||||
"ASSISTANTS": {
|
||||
"HEADER": "Assistants",
|
||||
"NO_ASSISTANTS_AVAILABLE": "There are no assistants available in your account.",
|
||||
"ADD_NEW": "Create a new assistant",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure to delete the assistant?",
|
||||
@@ -411,6 +431,10 @@
|
||||
"PLACEHOLDER": "Enter assistant name",
|
||||
"ERROR": "The name is required"
|
||||
},
|
||||
"TEMPERATURE": {
|
||||
"LABEL": "Response Temperature",
|
||||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "الوصف",
|
||||
"PLACEHOLDER": "Enter assistant description",
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "نظرة عامة على التسميات",
|
||||
"DESCRIPTION": "Track label performance with key metrics including conversations, response times, resolution times, and resolved cases. Click a label name for detailed insights.",
|
||||
"LOADING_CHART": "تحميل بيانات الرسم البياني...",
|
||||
"NO_ENOUGH_DATA": "لم يتم جمع بيانات بقدر كافي لإنشاء التقرير، الرجاء المحاولة مرة أخرى لاحقاً.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "تحميل تقارير التسمية",
|
||||
@@ -559,6 +560,7 @@
|
||||
"INBOX": "صندوق الوارد",
|
||||
"AGENT": "وكيل الدعم",
|
||||
"TEAM": "الفريق",
|
||||
"LABEL": "الوسم",
|
||||
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
|
||||
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"ALL": "الكل",
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"CONVERSATIONS": "المحادثات",
|
||||
"MESSAGES": "الرسائل"
|
||||
"MESSAGES": "الرسائل",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"CONVERSATIONS": "المحادثات",
|
||||
"MESSAGES": "الرسائل"
|
||||
"MESSAGES": "الرسائل",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
|
||||
@@ -76,7 +76,12 @@
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "رمز المصادقة",
|
||||
"NOTE": "يمكن استخدام هذا رمز المصادقة إذا كنت تبني تطبيقات API للتكامل مع Chatwoot",
|
||||
"COPY": "نسخ"
|
||||
"COPY": "نسخ",
|
||||
"RESET": "Reset",
|
||||
"CONFIRM_RESET": "Are you sure?",
|
||||
"CONFIRM_HINT": "Click again to confirm",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Alerts",
|
||||
@@ -185,7 +190,8 @@
|
||||
"OFFLINE": "غير متصل"
|
||||
},
|
||||
"SET_AVAILABILITY_SUCCESS": "تم تعيين التوافر بنجاح",
|
||||
"SET_AVAILABILITY_ERROR": "تعذر تعيين التوافر، الرجاء المحاولة مرة أخرى"
|
||||
"SET_AVAILABILITY_ERROR": "تعذر تعيين التوافر، الرجاء المحاولة مرة أخرى",
|
||||
"IMPERSONATING_ERROR": "Cannot change availability while impersonating a user"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "عنوان البريد الإلكتروني الخاص بك",
|
||||
@@ -280,6 +286,7 @@
|
||||
"REPORTS": "التقارير",
|
||||
"SETTINGS": "الإعدادات",
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"ACTIVE": "مفعل",
|
||||
"CAPTAIN": "قائد",
|
||||
"CAPTAIN_ASSISTANTS": "Assistants",
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
"ERROR_MESSAGE": "Could not update bot. Please try again."
|
||||
}
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"DESCRIPTION": "Copy the access token and save it securely",
|
||||
"COPY_SUCCESSFUL": "Access token copied to clipboard",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"FORM": {
|
||||
"AVATAR": {
|
||||
"LABEL": "Bot avatar"
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "{agentName} updated the account configuration (#{id})"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"DELETE": "{agentName} deleted conversation #{id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@
|
||||
"HEADER": {
|
||||
"TITLE": "Contacts",
|
||||
"SEARCH_TITLE": "Search contacts",
|
||||
"ACTIVE_TITLE": "Active contacts",
|
||||
"SEARCH_PLACEHOLDER": "Search...",
|
||||
"MESSAGE_BUTTON": "Message",
|
||||
"SEND_MESSAGE": "Send message",
|
||||
@@ -457,6 +458,10 @@
|
||||
"PLACEHOLDER": "Add Twitter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE_CONTACT": {
|
||||
"MESSAGE": "This action is permanent and irreversible.",
|
||||
"BUTTON": "Delete now"
|
||||
}
|
||||
},
|
||||
"DETAILS": {
|
||||
@@ -466,7 +471,7 @@
|
||||
"DELETE_CONTACT": "Delete contact",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Confirm Deletion",
|
||||
"DESCRIPTION": "Are you sure you want to delete this {contactName} contact?",
|
||||
"DESCRIPTION": "Are you sure you want to delete this contact?",
|
||||
"CONFIRM": "Yes, Delete",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Contact deleted successfully",
|
||||
@@ -555,7 +560,8 @@
|
||||
"SUBTITLE": "Start adding new contacts by clicking on the button below",
|
||||
"BUTTON_LABEL": "Add contact",
|
||||
"SEARCH_EMPTY_STATE_TITLE": "No contacts matches your search 🔍",
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋"
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋",
|
||||
"ACTIVE_EMPTY_STATE_TITLE": "No contacts are active at the moment 🌙"
|
||||
}
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"RESOLVE_ACTION": "Resolve",
|
||||
"REOPEN_ACTION": "Reopen",
|
||||
"OPEN_ACTION": "Open",
|
||||
"MORE_ACTIONS": "More actions",
|
||||
"OPEN": "More",
|
||||
"CLOSE": "Close",
|
||||
"DETAILS": "details",
|
||||
@@ -117,6 +118,11 @@
|
||||
"FAILED": "Couldn't change priority. Please try again."
|
||||
}
|
||||
},
|
||||
"DELETE_CONVERSATION": {
|
||||
"TITLE": "Delete conversation #{conversationId}",
|
||||
"DESCRIPTION": "Are you sure you want to delete this conversation?",
|
||||
"CONFIRM": "Delete"
|
||||
},
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "Mark as pending",
|
||||
"RESOLVED": "Mark as resolved",
|
||||
@@ -133,6 +139,7 @@
|
||||
"ASSIGN_LABEL": "Assign label",
|
||||
"AGENTS_LOADING": "Loading agents...",
|
||||
"ASSIGN_TEAM": "Assign team",
|
||||
"DELETE": "Delete conversation",
|
||||
"API": {
|
||||
"AGENT_ASSIGNMENT": {
|
||||
"SUCCESFUL": "Conversation id {conversationId} assigned to \"{agentName}\"",
|
||||
@@ -207,6 +214,8 @@
|
||||
"ASSIGN_LABEL_SUCCESFUL": "Label assigned successfully",
|
||||
"ASSIGN_LABEL_FAILED": "Label assignment failed",
|
||||
"CHANGE_TEAM": "Conversation team changed",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Conversation deleted successfully",
|
||||
"FAIL_DELETE_CONVERSATION": "Couldn't delete conversation! Try again",
|
||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB attachment limit",
|
||||
"MESSAGE_ERROR": "Unable to send this message, please try again later",
|
||||
"SENT_BY": "Sent by:",
|
||||
@@ -299,6 +308,7 @@
|
||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||
"PREVIOUS_CONVERSATION": "Previous Conversations",
|
||||
"MACROS": "Macros",
|
||||
"LINEAR_ISSUES": "Linked Linear Issues",
|
||||
"SHOPIFY_ORDERS": "Shopify Orders"
|
||||
},
|
||||
"SHOPIFY": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"PHONE_INPUT": {
|
||||
"PLACEHOLDER": "Search",
|
||||
"EMPTY_STATE": "No results found"
|
||||
}
|
||||
},
|
||||
"CLOSE": "Close"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,31 @@
|
||||
},
|
||||
"AUTO_RESOLVE": {
|
||||
"TITLE": "Auto-resolve conversations",
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period. Set the duration and customize the message to the user below."
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period of inactivity.",
|
||||
"DURATION": {
|
||||
"LABEL": "Inactivity duration",
|
||||
"HELP": "Time period of inactivity after which conversation is auto-resolved",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Auto resolve duration should be between 10 minutes and 999 days",
|
||||
"API": {
|
||||
"SUCCESS": "Auto resolve settings updated successfully",
|
||||
"ERROR": "Failed to update auto resolve settings"
|
||||
}
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Custom auto-resolution message",
|
||||
"PLACEHOLDER": "Conversation was marked resolved by system due to 15 days of inactivity",
|
||||
"HELP": "Message sent to the customer after conversation is auto-resolved"
|
||||
},
|
||||
"PREFERENCES": "Preferences",
|
||||
"LABEL": {
|
||||
"LABEL": "Add label after auto-resolution",
|
||||
"PLACEHOLDER": "Select a label"
|
||||
},
|
||||
"IGNORE_WAITING": {
|
||||
"LABEL": "Skip conversations waiting for agent’s reply"
|
||||
},
|
||||
"UPDATE_BUTTON": "Save Changes"
|
||||
},
|
||||
"NAME": {
|
||||
"LABEL": "Account name",
|
||||
@@ -70,7 +94,15 @@
|
||||
},
|
||||
"AUTO_RESOLVE_IGNORE_WAITING": {
|
||||
"LABEL": "Exclude unattended conversations",
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent’s reply."
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent's reply."
|
||||
},
|
||||
"AUDIO_TRANSCRIPTION": {
|
||||
"TITLE": "Transcribe Audio Messages",
|
||||
"NOTE": "Automatically transcribe audio messages in conversations. Generate a text transcript whenever an audio message is sent or received, and display it alongside the message.",
|
||||
"API": {
|
||||
"SUCCESS": "Audio transcription setting updated successfully",
|
||||
"ERROR": "Failed to update audio transcription setting"
|
||||
}
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Inactivity duration for resolution",
|
||||
|
||||
@@ -318,11 +318,18 @@
|
||||
"SUCCESS": "Issue unlinked successfully",
|
||||
"ERROR": "There was an error unlinking the issue, please try again"
|
||||
},
|
||||
"NO_LINKED_ISSUES": "No linked issues found",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Yes, delete",
|
||||
"CANCEL": "Cancel"
|
||||
},
|
||||
"CTA": {
|
||||
"TITLE": "Connect to Linear",
|
||||
"AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
|
||||
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
|
||||
"BUTTON_TEXT": "Connect Linear workspace"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -330,12 +337,17 @@
|
||||
"NAME": "Captain",
|
||||
"HEADER_KNOW_MORE": "Know more",
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
"PANEL_TITLE": "Get started with Copilot",
|
||||
"KICK_OFF_MESSAGE": "Need a quick summary, want to check past conversations, or draft a better reply? Copilot’s here to speed things up.",
|
||||
"SEND_MESSAGE": "Send message...",
|
||||
"EMPTY_MESSAGE": "There was an error generating the response. Please try again.",
|
||||
"LOADER": "Captain is thinking",
|
||||
"YOU": "You",
|
||||
"USE": "Use this",
|
||||
"RESET": "Reset",
|
||||
"SHOW_STEPS": "Show steps",
|
||||
"SELECT_ASSISTANT": "Select Assistant",
|
||||
"PROMPTS": {
|
||||
"SUMMARIZE": {
|
||||
@@ -349,6 +361,14 @@
|
||||
"RATE": {
|
||||
"LABEL": "Rate this conversation",
|
||||
"CONTENT": "Review the conversation to see how well it meets the customer's needs. Share a rating out of 5 based on tone, clarity, and effectiveness."
|
||||
},
|
||||
"HIGH_PRIORITY": {
|
||||
"LABEL": "High priority conversations",
|
||||
"CONTENT": "Give me a summary of all high priority open conversations. Include the conversation ID, customer name (if available), last message content, and assigned agent. Group by status if relevant."
|
||||
},
|
||||
"LIST_CONTACTS": {
|
||||
"LABEL": "List contacts",
|
||||
"CONTENT": "Show me the list of top 10 contacts. Include name, email or phone number (if available), last seen time, tags (if any)."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,7 +388,6 @@
|
||||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||
},
|
||||
@@ -383,6 +402,7 @@
|
||||
},
|
||||
"ASSISTANTS": {
|
||||
"HEADER": "Assistants",
|
||||
"NO_ASSISTANTS_AVAILABLE": "There are no assistants available in your account.",
|
||||
"ADD_NEW": "Create a new assistant",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure to delete the assistant?",
|
||||
@@ -411,6 +431,10 @@
|
||||
"PLACEHOLDER": "Enter assistant name",
|
||||
"ERROR": "The name is required"
|
||||
},
|
||||
"TEMPERATURE": {
|
||||
"LABEL": "Response Temperature",
|
||||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter assistant description",
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "Labels Overview",
|
||||
"DESCRIPTION": "Track label performance with key metrics including conversations, response times, resolution times, and resolved cases. Click a label name for detailed insights.",
|
||||
"LOADING_CHART": "Loading chart data...",
|
||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "Download label reports",
|
||||
@@ -559,6 +560,7 @@
|
||||
"INBOX": "Inbox",
|
||||
"AGENT": "Agent",
|
||||
"TEAM": "Team",
|
||||
"LABEL": "Label",
|
||||
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
|
||||
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"ALL": "All",
|
||||
"CONTACTS": "Contacts",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "Contacts",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
|
||||
@@ -76,7 +76,12 @@
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"NOTE": "This token can be used if you are building an API based integration",
|
||||
"COPY": "Copy"
|
||||
"COPY": "Copy",
|
||||
"RESET": "Reset",
|
||||
"CONFIRM_RESET": "Are you sure?",
|
||||
"CONFIRM_HINT": "Click again to confirm",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Alerts",
|
||||
@@ -185,7 +190,8 @@
|
||||
"OFFLINE": "Offline"
|
||||
},
|
||||
"SET_AVAILABILITY_SUCCESS": "Availability has been set successfully",
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again"
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again",
|
||||
"IMPERSONATING_ERROR": "Cannot change availability while impersonating a user"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Your email address",
|
||||
@@ -280,6 +286,7 @@
|
||||
"REPORTS": "Reports",
|
||||
"SETTINGS": "Settings",
|
||||
"CONTACTS": "Contacts",
|
||||
"ACTIVE": "Active",
|
||||
"CAPTAIN": "Captain",
|
||||
"CAPTAIN_ASSISTANTS": "Assistants",
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
"ERROR_MESSAGE": "Could not update bot. Please try again."
|
||||
}
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"DESCRIPTION": "Copy the access token and save it securely",
|
||||
"COPY_SUCCESSFUL": "Access token copied to clipboard",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"FORM": {
|
||||
"AVATAR": {
|
||||
"LABEL": "Bot avatar"
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "{agentName} updated the account configuration (#{id})"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"DELETE": "{agentName} deleted conversation #{id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@
|
||||
"HEADER": {
|
||||
"TITLE": "Контакти",
|
||||
"SEARCH_TITLE": "Search contacts",
|
||||
"ACTIVE_TITLE": "Active contacts",
|
||||
"SEARCH_PLACEHOLDER": "Search...",
|
||||
"MESSAGE_BUTTON": "Съобщение",
|
||||
"SEND_MESSAGE": "Изпрати съобщение",
|
||||
@@ -457,6 +458,10 @@
|
||||
"PLACEHOLDER": "Add Twitter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE_CONTACT": {
|
||||
"MESSAGE": "This action is permanent and irreversible.",
|
||||
"BUTTON": "Delete now"
|
||||
}
|
||||
},
|
||||
"DETAILS": {
|
||||
@@ -466,7 +471,7 @@
|
||||
"DELETE_CONTACT": "Изтриване на контакта",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Потвърди изтриването",
|
||||
"DESCRIPTION": "Are you sure you want to delete this {contactName} contact?",
|
||||
"DESCRIPTION": "Are you sure you want to delete this contact?",
|
||||
"CONFIRM": "Да, изтрий",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Контакта е изтрит успешно",
|
||||
@@ -555,7 +560,8 @@
|
||||
"SUBTITLE": "Start adding new contacts by clicking on the button below",
|
||||
"BUTTON_LABEL": "Add contact",
|
||||
"SEARCH_EMPTY_STATE_TITLE": "Няма контакти отговарящи на търсенети ви 🔍",
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋"
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋",
|
||||
"ACTIVE_EMPTY_STATE_TITLE": "No contacts are active at the moment 🌙"
|
||||
}
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"RESOLVE_ACTION": "Resolve",
|
||||
"REOPEN_ACTION": "Reopen",
|
||||
"OPEN_ACTION": "Отворен",
|
||||
"MORE_ACTIONS": "More actions",
|
||||
"OPEN": "More",
|
||||
"CLOSE": "Close",
|
||||
"DETAILS": "details",
|
||||
@@ -117,6 +118,11 @@
|
||||
"FAILED": "Couldn't change priority. Please try again."
|
||||
}
|
||||
},
|
||||
"DELETE_CONVERSATION": {
|
||||
"TITLE": "Delete conversation #{conversationId}",
|
||||
"DESCRIPTION": "Are you sure you want to delete this conversation?",
|
||||
"CONFIRM": "Изтрий"
|
||||
},
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "Mark as pending",
|
||||
"RESOLVED": "Mark as resolved",
|
||||
@@ -133,6 +139,7 @@
|
||||
"ASSIGN_LABEL": "Assign label",
|
||||
"AGENTS_LOADING": "Loading agents...",
|
||||
"ASSIGN_TEAM": "Assign team",
|
||||
"DELETE": "Delete conversation",
|
||||
"API": {
|
||||
"AGENT_ASSIGNMENT": {
|
||||
"SUCCESFUL": "Conversation id {conversationId} assigned to \"{agentName}\"",
|
||||
@@ -207,6 +214,8 @@
|
||||
"ASSIGN_LABEL_SUCCESFUL": "Label assigned successfully",
|
||||
"ASSIGN_LABEL_FAILED": "Label assignment failed",
|
||||
"CHANGE_TEAM": "Conversation team changed",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Conversation deleted successfully",
|
||||
"FAIL_DELETE_CONVERSATION": "Couldn't delete conversation! Try again",
|
||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB attachment limit",
|
||||
"MESSAGE_ERROR": "Unable to send this message, please try again later",
|
||||
"SENT_BY": "Sent by:",
|
||||
@@ -299,6 +308,7 @@
|
||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||
"PREVIOUS_CONVERSATION": "Предишни разговори",
|
||||
"MACROS": "Macros",
|
||||
"LINEAR_ISSUES": "Linked Linear Issues",
|
||||
"SHOPIFY_ORDERS": "Shopify Orders"
|
||||
},
|
||||
"SHOPIFY": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"PHONE_INPUT": {
|
||||
"PLACEHOLDER": "Търсене",
|
||||
"EMPTY_STATE": "Няма намерени резултати"
|
||||
}
|
||||
},
|
||||
"CLOSE": "Close"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,31 @@
|
||||
},
|
||||
"AUTO_RESOLVE": {
|
||||
"TITLE": "Auto-resolve conversations",
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period. Set the duration and customize the message to the user below."
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period of inactivity.",
|
||||
"DURATION": {
|
||||
"LABEL": "Inactivity duration",
|
||||
"HELP": "Time period of inactivity after which conversation is auto-resolved",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Auto resolve duration should be between 10 minutes and 999 days",
|
||||
"API": {
|
||||
"SUCCESS": "Auto resolve settings updated successfully",
|
||||
"ERROR": "Failed to update auto resolve settings"
|
||||
}
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Custom auto-resolution message",
|
||||
"PLACEHOLDER": "Conversation was marked resolved by system due to 15 days of inactivity",
|
||||
"HELP": "Message sent to the customer after conversation is auto-resolved"
|
||||
},
|
||||
"PREFERENCES": "Preferences",
|
||||
"LABEL": {
|
||||
"LABEL": "Add label after auto-resolution",
|
||||
"PLACEHOLDER": "Select a label"
|
||||
},
|
||||
"IGNORE_WAITING": {
|
||||
"LABEL": "Skip conversations waiting for agent’s reply"
|
||||
},
|
||||
"UPDATE_BUTTON": "Save Changes"
|
||||
},
|
||||
"NAME": {
|
||||
"LABEL": "Account name",
|
||||
@@ -70,7 +94,15 @@
|
||||
},
|
||||
"AUTO_RESOLVE_IGNORE_WAITING": {
|
||||
"LABEL": "Exclude unattended conversations",
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent’s reply."
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent's reply."
|
||||
},
|
||||
"AUDIO_TRANSCRIPTION": {
|
||||
"TITLE": "Transcribe Audio Messages",
|
||||
"NOTE": "Automatically transcribe audio messages in conversations. Generate a text transcript whenever an audio message is sent or received, and display it alongside the message.",
|
||||
"API": {
|
||||
"SUCCESS": "Audio transcription setting updated successfully",
|
||||
"ERROR": "Failed to update audio transcription setting"
|
||||
}
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Inactivity duration for resolution",
|
||||
|
||||
@@ -318,11 +318,18 @@
|
||||
"SUCCESS": "Issue unlinked successfully",
|
||||
"ERROR": "There was an error unlinking the issue, please try again"
|
||||
},
|
||||
"NO_LINKED_ISSUES": "No linked issues found",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Yes, delete",
|
||||
"CANCEL": "Отмени"
|
||||
},
|
||||
"CTA": {
|
||||
"TITLE": "Connect to Linear",
|
||||
"AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
|
||||
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
|
||||
"BUTTON_TEXT": "Connect Linear workspace"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -330,12 +337,17 @@
|
||||
"NAME": "Captain",
|
||||
"HEADER_KNOW_MORE": "Know more",
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
"PANEL_TITLE": "Get started with Copilot",
|
||||
"KICK_OFF_MESSAGE": "Need a quick summary, want to check past conversations, or draft a better reply? Copilot’s here to speed things up.",
|
||||
"SEND_MESSAGE": "Изпрати съобщение...",
|
||||
"EMPTY_MESSAGE": "There was an error generating the response. Please try again.",
|
||||
"LOADER": "Captain is thinking",
|
||||
"YOU": "You",
|
||||
"USE": "Use this",
|
||||
"RESET": "Reset",
|
||||
"SHOW_STEPS": "Show steps",
|
||||
"SELECT_ASSISTANT": "Select Assistant",
|
||||
"PROMPTS": {
|
||||
"SUMMARIZE": {
|
||||
@@ -349,6 +361,14 @@
|
||||
"RATE": {
|
||||
"LABEL": "Rate this conversation",
|
||||
"CONTENT": "Review the conversation to see how well it meets the customer's needs. Share a rating out of 5 based on tone, clarity, and effectiveness."
|
||||
},
|
||||
"HIGH_PRIORITY": {
|
||||
"LABEL": "High priority conversations",
|
||||
"CONTENT": "Give me a summary of all high priority open conversations. Include the conversation ID, customer name (if available), last message content, and assigned agent. Group by status if relevant."
|
||||
},
|
||||
"LIST_CONTACTS": {
|
||||
"LABEL": "List contacts",
|
||||
"CONTENT": "Show me the list of top 10 contacts. Include name, email or phone number (if available), last seen time, tags (if any)."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,7 +388,6 @@
|
||||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||
},
|
||||
@@ -383,6 +402,7 @@
|
||||
},
|
||||
"ASSISTANTS": {
|
||||
"HEADER": "Assistants",
|
||||
"NO_ASSISTANTS_AVAILABLE": "There are no assistants available in your account.",
|
||||
"ADD_NEW": "Create a new assistant",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure to delete the assistant?",
|
||||
@@ -411,6 +431,10 @@
|
||||
"PLACEHOLDER": "Enter assistant name",
|
||||
"ERROR": "The name is required"
|
||||
},
|
||||
"TEMPERATURE": {
|
||||
"LABEL": "Response Temperature",
|
||||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Описание",
|
||||
"PLACEHOLDER": "Enter assistant description",
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "Labels Overview",
|
||||
"DESCRIPTION": "Track label performance with key metrics including conversations, response times, resolution times, and resolved cases. Click a label name for detailed insights.",
|
||||
"LOADING_CHART": "Loading chart data...",
|
||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "Download label reports",
|
||||
@@ -559,6 +560,7 @@
|
||||
"INBOX": "Входяща кутия",
|
||||
"AGENT": "Агент",
|
||||
"TEAM": "Team",
|
||||
"LABEL": "Label",
|
||||
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
|
||||
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"ALL": "Всички",
|
||||
"CONTACTS": "Контакти",
|
||||
"CONVERSATIONS": "Разговори",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "Контакти",
|
||||
"CONVERSATIONS": "Разговори",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
|
||||
@@ -76,7 +76,12 @@
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"NOTE": "This token can be used if you are building an API based integration",
|
||||
"COPY": "Copy"
|
||||
"COPY": "Copy",
|
||||
"RESET": "Reset",
|
||||
"CONFIRM_RESET": "Are you sure?",
|
||||
"CONFIRM_HINT": "Click again to confirm",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Alerts",
|
||||
@@ -185,7 +190,8 @@
|
||||
"OFFLINE": "Offline"
|
||||
},
|
||||
"SET_AVAILABILITY_SUCCESS": "Availability has been set successfully",
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again"
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again",
|
||||
"IMPERSONATING_ERROR": "Cannot change availability while impersonating a user"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Your email address",
|
||||
@@ -280,6 +286,7 @@
|
||||
"REPORTS": "Reports",
|
||||
"SETTINGS": "Settings",
|
||||
"CONTACTS": "Контакти",
|
||||
"ACTIVE": "Активен",
|
||||
"CAPTAIN": "Captain",
|
||||
"CAPTAIN_ASSISTANTS": "Assistants",
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
"ERROR_MESSAGE": "No s'ha pogut actualitzar el bot. Torneu-ho a provar."
|
||||
}
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Token d'accés",
|
||||
"DESCRIPTION": "Copy the access token and save it securely",
|
||||
"COPY_SUCCESSFUL": "Access token copied to clipboard",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"FORM": {
|
||||
"AVATAR": {
|
||||
"LABEL": "Bot avatar"
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "{agentName} updated the account configuration (#{id})"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"DELETE": "{agentName} deleted conversation #{id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@
|
||||
"HEADER": {
|
||||
"TITLE": "Contactes",
|
||||
"SEARCH_TITLE": "Search contacts",
|
||||
"ACTIVE_TITLE": "Active contacts",
|
||||
"SEARCH_PLACEHOLDER": "Search...",
|
||||
"MESSAGE_BUTTON": "Missatge",
|
||||
"SEND_MESSAGE": "Envia missatge",
|
||||
@@ -457,6 +458,10 @@
|
||||
"PLACEHOLDER": "Add Twitter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE_CONTACT": {
|
||||
"MESSAGE": "This action is permanent and irreversible.",
|
||||
"BUTTON": "Delete now"
|
||||
}
|
||||
},
|
||||
"DETAILS": {
|
||||
@@ -466,7 +471,7 @@
|
||||
"DELETE_CONTACT": "Contacte esborrat",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Confirma l'esborrat",
|
||||
"DESCRIPTION": "Are you sure you want to delete this {contactName} contact?",
|
||||
"DESCRIPTION": "Are you sure you want to delete this contact?",
|
||||
"CONFIRM": "Si, esborra",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Contacte esborrat correctament",
|
||||
@@ -555,7 +560,8 @@
|
||||
"SUBTITLE": "Start adding new contacts by clicking on the button below",
|
||||
"BUTTON_LABEL": "Add contact",
|
||||
"SEARCH_EMPTY_STATE_TITLE": "No hi ha cap contacte que coincideixi amb la vostra cerca 🔍",
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋"
|
||||
"LIST_EMPTY_STATE_TITLE": "No contacts available in this view 📋",
|
||||
"ACTIVE_EMPTY_STATE_TITLE": "No contacts are active at the moment 🌙"
|
||||
}
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"RESOLVE_ACTION": "Resoldre",
|
||||
"REOPEN_ACTION": "Tornar a obrir",
|
||||
"OPEN_ACTION": "Obrir",
|
||||
"MORE_ACTIONS": "More actions",
|
||||
"OPEN": "Més",
|
||||
"CLOSE": "Tanca",
|
||||
"DETAILS": "detalls",
|
||||
@@ -117,6 +118,11 @@
|
||||
"FAILED": "No s'ha pogut canviar la prioritat. Si us plau, torna-ho a provar."
|
||||
}
|
||||
},
|
||||
"DELETE_CONVERSATION": {
|
||||
"TITLE": "Delete conversation #{conversationId}",
|
||||
"DESCRIPTION": "Are you sure you want to delete this conversation?",
|
||||
"CONFIRM": "Esborrar"
|
||||
},
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "Marca com a pendent",
|
||||
"RESOLVED": "Marca com a resolt",
|
||||
@@ -133,6 +139,7 @@
|
||||
"ASSIGN_LABEL": "Assigna etiqueta",
|
||||
"AGENTS_LOADING": "S'estan carregant els agents...",
|
||||
"ASSIGN_TEAM": "Assigna un equip",
|
||||
"DELETE": "Delete conversation",
|
||||
"API": {
|
||||
"AGENT_ASSIGNMENT": {
|
||||
"SUCCESFUL": "Id de conversa {conversationId} assignat a \"{agentName}\"",
|
||||
@@ -207,6 +214,8 @@
|
||||
"ASSIGN_LABEL_SUCCESFUL": "L'etiqueta s'ha assignat correctament",
|
||||
"ASSIGN_LABEL_FAILED": "L'assignació d'etiquetes ha fallat",
|
||||
"CHANGE_TEAM": "L'equip de conversa ha canviat",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Conversation deleted successfully",
|
||||
"FAIL_DELETE_CONVERSATION": "Couldn't delete conversation! Try again",
|
||||
"FILE_SIZE_LIMIT": "El fitxer supera el límit de {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB fitxers adjunts",
|
||||
"MESSAGE_ERROR": "No es pot enviar aquest missatge, torna-ho a provar més tard",
|
||||
"SENT_BY": "Enviat per:",
|
||||
@@ -299,6 +308,7 @@
|
||||
"CONTACT_ATTRIBUTES": "Atributs de contacte",
|
||||
"PREVIOUS_CONVERSATION": "Converses prèvies",
|
||||
"MACROS": "Macros",
|
||||
"LINEAR_ISSUES": "Linked Linear Issues",
|
||||
"SHOPIFY_ORDERS": "Shopify Orders"
|
||||
},
|
||||
"SHOPIFY": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"PHONE_INPUT": {
|
||||
"PLACEHOLDER": "Cercar",
|
||||
"EMPTY_STATE": "No s'ha trobat agents"
|
||||
}
|
||||
},
|
||||
"CLOSE": "Tanca"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,31 @@
|
||||
},
|
||||
"AUTO_RESOLVE": {
|
||||
"TITLE": "Auto-resolve conversations",
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period. Set the duration and customize the message to the user below."
|
||||
"NOTE": "This configuration would allow you to automatically resolve the conversation after a certain period of inactivity.",
|
||||
"DURATION": {
|
||||
"LABEL": "Inactivity duration",
|
||||
"HELP": "Time period of inactivity after which conversation is auto-resolved",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Auto resolve duration should be between 10 minutes and 999 days",
|
||||
"API": {
|
||||
"SUCCESS": "Auto resolve settings updated successfully",
|
||||
"ERROR": "Failed to update auto resolve settings"
|
||||
}
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Custom auto-resolution message",
|
||||
"PLACEHOLDER": "Conversation was marked resolved by system due to 15 days of inactivity",
|
||||
"HELP": "Message sent to the customer after conversation is auto-resolved"
|
||||
},
|
||||
"PREFERENCES": "Preferences",
|
||||
"LABEL": {
|
||||
"LABEL": "Add label after auto-resolution",
|
||||
"PLACEHOLDER": "Select a label"
|
||||
},
|
||||
"IGNORE_WAITING": {
|
||||
"LABEL": "Skip conversations waiting for agent’s reply"
|
||||
},
|
||||
"UPDATE_BUTTON": "Save Changes"
|
||||
},
|
||||
"NAME": {
|
||||
"LABEL": "Nom del compte",
|
||||
@@ -70,7 +94,15 @@
|
||||
},
|
||||
"AUTO_RESOLVE_IGNORE_WAITING": {
|
||||
"LABEL": "Exclude unattended conversations",
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent’s reply."
|
||||
"HELP": "When enabled, the system will skip resolving conversations that are still waiting for an agent's reply."
|
||||
},
|
||||
"AUDIO_TRANSCRIPTION": {
|
||||
"TITLE": "Transcribe Audio Messages",
|
||||
"NOTE": "Automatically transcribe audio messages in conversations. Generate a text transcript whenever an audio message is sent or received, and display it alongside the message.",
|
||||
"API": {
|
||||
"SUCCESS": "Audio transcription setting updated successfully",
|
||||
"ERROR": "Failed to update audio transcription setting"
|
||||
}
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Inactivity duration for resolution",
|
||||
|
||||
@@ -318,11 +318,18 @@
|
||||
"SUCCESS": "S'ha desenllaçat la issue correctament",
|
||||
"ERROR": "S'ha produït un error en desenllaçar la issue, torna-ho a provar"
|
||||
},
|
||||
"NO_LINKED_ISSUES": "No linked issues found",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Sí, esborra",
|
||||
"CANCEL": "Cancel·la"
|
||||
},
|
||||
"CTA": {
|
||||
"TITLE": "Connect to Linear",
|
||||
"AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
|
||||
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
|
||||
"BUTTON_TEXT": "Connect Linear workspace"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -330,12 +337,17 @@
|
||||
"NAME": "Captain",
|
||||
"HEADER_KNOW_MORE": "Know more",
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
"PANEL_TITLE": "Get started with Copilot",
|
||||
"KICK_OFF_MESSAGE": "Need a quick summary, want to check past conversations, or draft a better reply? Copilot’s here to speed things up.",
|
||||
"SEND_MESSAGE": "Envia missatge...",
|
||||
"EMPTY_MESSAGE": "There was an error generating the response. Please try again.",
|
||||
"LOADER": "Captain is thinking",
|
||||
"YOU": "Tu",
|
||||
"USE": "Use this",
|
||||
"RESET": "Reset",
|
||||
"SHOW_STEPS": "Show steps",
|
||||
"SELECT_ASSISTANT": "Select Assistant",
|
||||
"PROMPTS": {
|
||||
"SUMMARIZE": {
|
||||
@@ -349,6 +361,14 @@
|
||||
"RATE": {
|
||||
"LABEL": "Rate this conversation",
|
||||
"CONTENT": "Review the conversation to see how well it meets the customer's needs. Share a rating out of 5 based on tone, clarity, and effectiveness."
|
||||
},
|
||||
"HIGH_PRIORITY": {
|
||||
"LABEL": "High priority conversations",
|
||||
"CONTENT": "Give me a summary of all high priority open conversations. Include the conversation ID, customer name (if available), last message content, and assigned agent. Group by status if relevant."
|
||||
},
|
||||
"LIST_CONTACTS": {
|
||||
"LABEL": "List contacts",
|
||||
"CONTENT": "Show me the list of top 10 contacts. Include name, email or phone number (if available), last seen time, tags (if any)."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -368,7 +388,6 @@
|
||||
"CANCEL_ANYTIME": "Pots canviar o cancel·lar el teu pla en qualsevol moment"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Posa't en contacte amb el vostre administrador per obtenir l'actualització."
|
||||
},
|
||||
@@ -383,6 +402,7 @@
|
||||
},
|
||||
"ASSISTANTS": {
|
||||
"HEADER": "Assistants",
|
||||
"NO_ASSISTANTS_AVAILABLE": "There are no assistants available in your account.",
|
||||
"ADD_NEW": "Create a new assistant",
|
||||
"DELETE": {
|
||||
"TITLE": "Are you sure to delete the assistant?",
|
||||
@@ -411,6 +431,10 @@
|
||||
"PLACEHOLDER": "Enter assistant name",
|
||||
"ERROR": "The name is required"
|
||||
},
|
||||
"TEMPERATURE": {
|
||||
"LABEL": "Response Temperature",
|
||||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Descripció",
|
||||
"PLACEHOLDER": "Enter assistant description",
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "Visió general de les etiquetes",
|
||||
"DESCRIPTION": "Track label performance with key metrics including conversations, response times, resolution times, and resolved cases. Click a label name for detailed insights.",
|
||||
"LOADING_CHART": "S'estan carregant dades del gràfic...",
|
||||
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "Descarregar Informes d'etiquetes",
|
||||
@@ -559,6 +560,7 @@
|
||||
"INBOX": "Safata d'entrada",
|
||||
"AGENT": "Agent",
|
||||
"TEAM": "Equip",
|
||||
"LABEL": "Etiqueta",
|
||||
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
|
||||
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"ALL": "Totes",
|
||||
"CONTACTS": "Contactes",
|
||||
"CONVERSATIONS": "Converses",
|
||||
"MESSAGES": "Missatges"
|
||||
"MESSAGES": "Missatges",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "Contactes",
|
||||
"CONVERSATIONS": "Converses",
|
||||
"MESSAGES": "Missatges"
|
||||
"MESSAGES": "Missatges",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
|
||||
@@ -76,7 +76,12 @@
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Token d'accés",
|
||||
"NOTE": "Aquest token es pot utilitzar si creeu una integració basada en l'API",
|
||||
"COPY": "Copia"
|
||||
"COPY": "Copia",
|
||||
"RESET": "Reset",
|
||||
"CONFIRM_RESET": "Are you sure?",
|
||||
"CONFIRM_HINT": "Click again to confirm",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Alerts",
|
||||
@@ -185,7 +190,8 @@
|
||||
"OFFLINE": "Fora de línia"
|
||||
},
|
||||
"SET_AVAILABILITY_SUCCESS": "La disponibilitat s'ha establert correctament",
|
||||
"SET_AVAILABILITY_ERROR": "No s'ha pogut establir la disponibilitat, torna-ho a provar"
|
||||
"SET_AVAILABILITY_ERROR": "No s'ha pogut establir la disponibilitat, torna-ho a provar",
|
||||
"IMPERSONATING_ERROR": "Cannot change availability while impersonating a user"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "La teva adreça de correu electrònic",
|
||||
@@ -280,6 +286,7 @@
|
||||
"REPORTS": "Informes",
|
||||
"SETTINGS": "Configuracions",
|
||||
"CONTACTS": "Contactes",
|
||||
"ACTIVE": "Actiu",
|
||||
"CAPTAIN": "Captain",
|
||||
"CAPTAIN_ASSISTANTS": "Assistants",
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
"ERROR_MESSAGE": "Could not update bot. Please try again."
|
||||
}
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Přístupový token",
|
||||
"DESCRIPTION": "Copy the access token and save it securely",
|
||||
"COPY_SUCCESSFUL": "Access token copied to clipboard",
|
||||
"RESET_SUCCESS": "Access token regenerated successfully",
|
||||
"RESET_ERROR": "Unable to regenerate access token. Please try again"
|
||||
},
|
||||
"FORM": {
|
||||
"AVATAR": {
|
||||
"LABEL": "Bot avatar"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user