Compare commits

...
Author SHA1 Message Date
Tanmay Deep Sharma f6f6263f3c Merge branch 'feat/assignment_revamp' into feat/assignment_revamp_fe 2025-08-01 00:16:10 +05:30
Tanmay Deep Sharma 5f03418ac9 fix agent rate limiting 2025-08-01 00:10:40 +05:30
Tanmay Deep Sharma 11370316b2 fix round robin assignment 2025-07-31 20:50:19 +05:30
Tanmay Deep Sharma 9a09df4563 fix rubcop validations 2025-07-31 17:11:14 +05:30
Tanmay Deep Sharma 364dbdd69f Merge branch 'feat/assignment_revamp' into feat/assignment_revamp_fe 2025-07-31 15:42:58 +05:30
Tanmay Deep Sharma f1832148fd fix rubcop validations 2025-07-31 15:36:06 +05:30
Tanmay Deep Sharma 8f9c45ea8c add assignment v2 UI 2025-07-31 14:58:10 +05:30
Tanmay Deep Sharma 24c7171a4e fix rspecs 2025-07-31 14:29:54 +05:30
Tanmay Deep Sharma b5f3daafc9 fix rubocop validations 2025-07-31 13:42:02 +05:30
Tanmay Deep Sharma 2c204326e5 backend fixes for assignment v2 2025-07-31 11:53:56 +05:30
Tanmay Deep Sharma a33ae58452 add crud for assignment and agent capacity 2025-07-30 12:08:03 +05:30
Tanmay Deep Sharma a96e75b904 add leave control 2025-07-30 11:47:40 +05:30
Tanmay Deep Sharma 5bb88192c4 add initial asssignment v2 changes, added capacity and agent policies 2025-07-30 10:08:08 +05:30
6475a6a593 feat: add references header to reply emails (#11719)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-29 15:54:14 +05:30
441cc065ae feat: add config for OpenAI endpoint (#12051)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-07-29 09:44:50 +05:30
Chatwoot BotandGitHub 0b74be82a3 chore: Update translations (#12037) 2025-07-28 17:33:49 +04:00
cbc2807296 fix: Creates contact when Instagram returns No matching Instagram user (#11496)
# Creates contact when Instagram returns `No matching Instagram user`

## Description

The error occurs when Facebook tries to validate the Facebook App
created to authorize Instagram integration.
The Facebook's agent uses a Bot to make tests on the App where is not a
valid user via API, returning `{"error"=>{"message"=>"No matching
Instagram user", "type"=>"IGApiException", "code"=>9010}}`.
Then Facebook rejects the request saying this app is still not ready
once the integration with Instagram didn't work.
We can safely create an unknown contact, making this integration work.

## Type of change

Please delete options that are not relevant.

- [X] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

There's automated test to cover.

## Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my code
- [X] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [X] I have added tests that prove my fix is effective or that my
feature works
- [X] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-07-28 13:49:13 +04:00
Shivam MishraandGitHub c09e875c83 feat: skip inbox filter if the user has access to all inboxes (#12043) 2025-07-25 15:29:10 +05:30
87313ecc35 fix: Add delay to instagram/messenger echo events to prevent duplicate messages (#12032)
- Add 2-second delay to Facebook Messenger echo event processing to
prevent race condition
- Add 2-second delay to Instagram echo event processing for consistency
- Prevent duplicate messages when echo events arrive before send message
API completes processing

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-07-24 21:11:02 +04:00
8262123481 feat: Remove subscription on WhatsApp inbox delete (#11977)
- remove webhook subscription while deleting a whatsapp inbox created
via embedded signup

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-07-24 14:04:19 +04:00
420be64c45 chore: Automate SSL with Cloudflare (#12021)
This PR adds support for automatic SSL issuance using Cloudflare when a
custom domain is updated.

- Introduced a cloudflare configuration. If present, the system will
attempt to issue an SSL certificate via Cloudflare whenever a custom
domain is added or changed.
- SSL verification is handled using an HTTP challenge.
- The job will store the HTTP challenge response provided by Cloudflare
and serve it under the /.well-known/cf path automatically.

How to test:

- Create a Cloudflare zone for your domain and copy the Zone ID.
- Generate a Cloudflare API token with the required SSL certificate
permissions.
- Set the Fallback Origin under SSL -> Custom HostName to the Chatwoot
installation.
- Add or update a custom domain and verify that the SSL certificate is
automatically issued.

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-07-24 13:09:06 +04:00
PranavandGitHub 9acb0d86b5 fix: Disable enqueueing Avatar jobs if the URL is invalid (#12035)
- Add a new queue purgable
- Disable enqueuing the job if URL is invalid
2025-07-24 12:56:39 +04:00
Vishnu NarayananandGitHub 2a5ecf84a1 chore: add sidekiq_alive gem for health check endpoint (#12008)
```
➜  chatwoot git:(feat/sidekiq-health) curl -I localhost:7433
HTTP/1.1 200 OK
Server: SidekiqAlive/2.5.0 (Ruby/3.4.4)
Connection: Keep-Alive
Date: Tue, 22 Jul 2025 10:34:28 GMT
Content-Length: 6

➜  chatwoot git:(feat/sidekiq-health) curl localhost:7433
Alive!%
```

fixes: https://github.com/chatwoot/chatwoot/issues/10948
2025-07-24 08:08:37 +04:00
286e3a449d feat: Introduce the crm_v2 feature flag for CRM changes (#12014)
Introduce crm_v2 feature flag for our upcoming optimisations for CRM

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-07-23 17:07:02 +04:00
Shivam MishraandGitHub e5ee6027b4 feat: don't add inbox condition for admins in search (#12028) 2025-07-23 16:30:07 +04:00
Sivin VargheseandGitHub eb412b67bd fix: Code component style issue (#12022)
# Pull Request Template

## Description

This PR fixes a styling issue in the code component that was introduced
after merging this
[PR](https://github.com/chatwoot/chatwoot/pull/12007/commits/07855d4369a4fcbe99bdc7dc7c1198cd279dafae)

**Screenshots**

**Before**
<img width="456" height="506" alt="image"
src="https://github.com/user-attachments/assets/6cd78708-93ad-4457-9e0f-8a25e3c7545c"
/>


**After**
<img width="456" height="506" alt="image"
src="https://github.com/user-attachments/assets/3bb059fa-5ed8-4d92-bf6e-56831fb2f55d"
/>
2025-07-23 13:20:46 +04:00
Sivin VargheseandGitHub 6fb762f96c chore: Migrate to next Switch component (#12005) 2025-07-23 13:56:17 +05:30
ab1ba1c4c7 feat: Add manual WhatsApp templates sync with UI (#12007)
Fixes https://github.com/chatwoot/chatwoot/issues/9600

**Summary**
- Added manual WhatsApp templates sync functionality accessible via UI
- Added refresh button in both inbox settings and template picker modal
- Enhanced template picker to always show for WhatsApp Cloud channels
(even when empty)


**Preview**

<img width="1456" height="798" alt="CleanShot 2025-07-22 at 14 15 28@2x"
src="https://github.com/user-attachments/assets/8a04ff26-61fa-42ee-a1b8-5e06433ae6e0"
/>


<img width="2376" height="1574" alt="CleanShot 2025-07-22 at 14 19
24@2x"
src="https://github.com/user-attachments/assets/1c772034-04ff-484d-88dd-ca8123e93065"
/>

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
2025-07-23 12:22:51 +04:00
Tanmay Deep SharmaandGitHub d276025419 fix: circle ci bundle audit (#12019) 2025-07-23 11:36:39 +04:00
298 changed files with 12398 additions and 402 deletions
+2
View File
@@ -121,6 +121,8 @@ gem 'sentry-sidekiq', '>= 5.19.0', require: false
gem 'sidekiq', '>= 7.3.1'
# We want cron jobs
gem 'sidekiq-cron', '>= 1.12.0'
# for sidekiq healthcheck
gem 'sidekiq_alive'
##-- Push notification service --##
gem 'fcm'
+11 -6
View File
@@ -359,6 +359,7 @@ GEM
grpc (1.72.0-x86_64-linux)
google-protobuf (>= 3.25, < 5.0)
googleapis-common-protos-types (~> 1.0)
gserver (0.0.1)
haikunator (1.1.1)
hairtrigger (1.0.0)
activerecord (>= 6.0, < 8)
@@ -477,7 +478,7 @@ GEM
mime-types-data (3.2023.0218.1)
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.8)
mini_portile2 (2.8.9)
minitest (5.25.5)
mock_redis (0.36.0)
ruby2_keywords
@@ -508,14 +509,14 @@ GEM
newrelic_rpm (9.6.0)
base64
nio4r (2.7.3)
nokogiri (1.18.8)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.8-arm64-darwin)
nokogiri (1.18.9-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-darwin)
nokogiri (1.18.9-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu)
nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
@@ -785,6 +786,9 @@ GEM
fugit (~> 1.8)
globalid (>= 1.0.1)
sidekiq (>= 6)
sidekiq_alive (2.5.0)
gserver (~> 0.0.1)
sidekiq (>= 5, < 9)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
@@ -823,7 +827,7 @@ GEM
stripe (8.5.0)
telephone_number (1.4.20)
test-prof (1.2.1)
thor (1.3.1)
thor (1.4.0)
tilt (2.3.0)
time_diff (0.3.0)
activesupport
@@ -1010,6 +1014,7 @@ DEPENDENCIES
shoulda-matchers
sidekiq (>= 7.3.1)
sidekiq-cron (>= 1.12.0)
sidekiq_alive
simplecov (= 0.17.1)
slack-ruby-client (~> 2.5.2)
spring
+9 -1
View File
@@ -6,6 +6,7 @@
# We don't want to update the name of the identified original contact.
class ContactIdentifyAction
include UrlHelper
pattr_initialize [:contact!, :params!, { retain_original_contact_name: false, discard_invalid_attrs: false }]
def perform
@@ -104,7 +105,14 @@ class ContactIdentifyAction
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
@contact.discard_invalid_attrs if discard_invalid_attrs
@contact.save!
Avatar::AvatarFromUrlJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present? && !@contact.avatar.attached?
enqueue_avatar_job
end
def enqueue_avatar_job
return unless params[:avatar_url].present? && !@contact.avatar.attached?
return unless url_valid?(params[:avatar_url])
Avatar::AvatarFromUrlJob.perform_later(@contact, params[:avatar_url])
end
def merge_contact(base_contact, merge_contact)
@@ -0,0 +1,176 @@
# frozen_string_literal: true
class Api::V1::Accounts::AgentCapacityPoliciesController < Api::V1::Accounts::BaseController
before_action :ensure_enterprise_account
before_action :fetch_agent_capacity_policy, only: [:show, :update, :destroy, :set_inbox_limit, :remove_inbox_limit, :assign_user, :remove_user]
before_action :check_authorization
def index
@agent_capacity_policies = Enterprise::AgentCapacityPolicy.where(account_id: Current.account.id).includes(:users, :inbox_capacity_limits)
render json: { agent_capacity_policies: serialize_agent_capacity_policies(@agent_capacity_policies) }
end
def show
render json: { agent_capacity_policy: serialize_agent_capacity_policy(@agent_capacity_policy) }
end
def create
@agent_capacity_policy = Enterprise::AgentCapacityPolicy.new(agent_capacity_policy_params)
@agent_capacity_policy.account_id = Current.account.id
if @agent_capacity_policy.save
render json: { agent_capacity_policy: serialize_agent_capacity_policy(@agent_capacity_policy) }, status: :created
else
render json: { errors: @agent_capacity_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @agent_capacity_policy.update(agent_capacity_policy_params)
render json: { agent_capacity_policy: serialize_agent_capacity_policy(@agent_capacity_policy) }
else
render json: { errors: @agent_capacity_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
if @agent_capacity_policy.destroy
head :ok
else
render json: { errors: @agent_capacity_policy.errors.full_messages }, status: :unprocessable_entity
end
end
# Set inbox capacity limit for a policy
def set_inbox_limit
inbox = Current.account.inboxes.find(params[:inbox_id])
inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find_or_initialize_by(inbox: inbox)
if inbox_limit.update(conversation_limit: params[:conversation_limit])
render json: {
inbox_capacity_limit: serialize_inbox_capacity_limit(inbox_limit)
}
else
render json: { errors: inbox_limit.errors.full_messages }, status: :unprocessable_entity
end
end
# Remove inbox capacity limit from a policy
def remove_inbox_limit
inbox = Current.account.inboxes.find(params[:inbox_id])
inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find_by(inbox: inbox)
if inbox_limit
if inbox_limit.destroy
head :ok
else
render json: { errors: inbox_limit.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: 'Inbox limit not found' }, status: :not_found
end
end
# Assign a user to a capacity policy
def assign_user
user = Current.account.users.find(params[:user_id])
account_user = Current.account.account_users.find_by!(user: user)
# Update the account_user to assign to this policy
if account_user.update(agent_capacity_policy_id: @agent_capacity_policy.id)
render json: {
message: 'User assigned successfully',
user_id: user.id,
agent_capacity_policy_id: @agent_capacity_policy.id
}
else
render json: { errors: account_user.errors.full_messages }, status: :unprocessable_entity
end
end
# Remove a user from a capacity policy
def remove_user
user = Current.account.users.find(params[:user_id])
account_user = Current.account.account_users.find_by!(user: user)
if account_user.agent_capacity_policy_id == @agent_capacity_policy.id
if account_user.update(agent_capacity_policy_id: nil)
head :ok
else
render json: { errors: account_user.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: 'User not assigned to this policy' }, status: :not_found
end
end
# Get current capacity status for an agent
def agent_capacity
user = Current.account.users.find(params[:agent_id])
inbox = params[:inbox_id] ? Current.account.inboxes.find(params[:inbox_id]) : nil
capacity_service = Enterprise::AssignmentV2::CapacityService.new
capacity_data = if inbox
capacity_service.get_agent_capacity(user, inbox)
else
capacity_service.get_agent_overall_capacity(user)
end
render json: { agent_capacity: capacity_data }
end
private
def ensure_enterprise_account
return if Current.account.feature_enabled?(:enterprise_agent_capacity)
render json: {
error: 'Agent capacity policies are only available for enterprise accounts'
}, status: :forbidden
end
def fetch_agent_capacity_policy
@agent_capacity_policy = Enterprise::AgentCapacityPolicy
.where(account_id: Current.account.id)
.includes(:users, inbox_capacity_limits: :inbox)
.find(params[:id])
end
def agent_capacity_policy_params
params.require(:agent_capacity_policy).permit(:name, :description, exclusion_rules: {})
end
def check_authorization
authorize(Enterprise::AgentCapacityPolicy) if defined?(Enterprise::AgentCapacityPolicy)
end
def serialize_agent_capacity_policy(policy)
{
id: policy.id,
name: policy.name,
description: policy.description,
exclusion_rules: policy.exclusion_rules,
user_count: policy.users.count,
inbox_limit_count: policy.inbox_capacity_limits.count,
inbox_limits: policy.inbox_capacity_limits.map { |limit| serialize_inbox_capacity_limit(limit) },
users: policy.users.map { |user| { id: user.id, name: user.name, email: user.email, avatar_url: user.avatar_url } },
created_at: policy.created_at,
updated_at: policy.updated_at
}
end
def serialize_agent_capacity_policies(policies)
policies.map { |policy| serialize_agent_capacity_policy(policy) }
end
def serialize_inbox_capacity_limit(limit)
{
id: limit.id,
inbox_id: limit.inbox_id,
inbox_name: limit.inbox.name,
conversation_limit: limit.conversation_limit,
created_at: limit.created_at,
updated_at: limit.updated_at
}
end
end
@@ -0,0 +1,79 @@
# frozen_string_literal: true
class Api::V1::Accounts::AssignmentPoliciesController < Api::V1::Accounts::BaseController
before_action :fetch_assignment_policy, only: [:show, :update, :destroy]
before_action :check_authorization
def index
@assignment_policies = Current.account.assignment_policies.includes(:inboxes)
render json: { assignment_policies: serialize_assignment_policies(@assignment_policies) }
end
def show
render json: { assignment_policy: serialize_assignment_policy(@assignment_policy) }
end
def create
@assignment_policy = Current.account.assignment_policies.build(assignment_policy_params)
if @assignment_policy.save
render json: { assignment_policy: serialize_assignment_policy(@assignment_policy) }, status: :created
else
render json: { errors: @assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @assignment_policy.update(assignment_policy_params)
render json: { assignment_policy: serialize_assignment_policy(@assignment_policy) }
else
render json: { errors: @assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
if @assignment_policy.destroy
head :ok
else
render json: { errors: @assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
private
def fetch_assignment_policy
@assignment_policy = Current.account.assignment_policies.find(params[:id])
end
def assignment_policy_params
params.require(:assignment_policy).permit(
:name, :description, :assignment_order, :conversation_priority,
:fair_distribution_limit, :fair_distribution_window, :enabled
)
end
def serialize_assignment_policy(policy)
{
id: policy.id,
name: policy.name,
description: policy.description,
assignment_order: policy.assignment_order,
conversation_priority: policy.conversation_priority,
fair_distribution_limit: policy.fair_distribution_limit,
fair_distribution_window: policy.fair_distribution_window,
enabled: policy.enabled,
inbox_count: policy.inboxes.count,
inboxes: policy.inboxes.map { |inbox| { id: inbox.id, name: inbox.name } },
created_at: policy.created_at,
updated_at: policy.updated_at
}
end
def serialize_assignment_policies(policies)
policies.map { |policy| serialize_assignment_policy(policy) }
end
def check_authorization
authorize(AssignmentPolicy)
end
end
@@ -122,7 +122,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
def resolved_contacts
return @resolved_contacts if @resolved_contacts
@resolved_contacts = Current.account.contacts.resolved_contacts
@resolved_contacts = Current.account.contacts.resolved_contacts(use_crm_v2: Current.account.feature_enabled?('crm_v2'))
@resolved_contacts = @resolved_contacts.tagged_with(params[:labels], any: true) if params[:labels].present?
@resolved_contacts
@@ -0,0 +1,81 @@
# frozen_string_literal: true
class Api::V1::Accounts::InboxAssignmentPoliciesController < Api::V1::Accounts::BaseController
before_action :fetch_inbox
before_action :check_authorization
def show
@inbox_assignment_policy = @inbox.inbox_assignment_policy
if @inbox_assignment_policy
render json: {
inbox_assignment_policy: serialize_inbox_assignment_policy(@inbox_assignment_policy)
}
else
render json: {
inbox_assignment_policy: nil,
message: 'No assignment policy assigned to this inbox'
}
end
end
def create
# Remove existing assignment if any
@inbox.inbox_assignment_policy&.destroy
@assignment_policy = Current.account.assignment_policies.find(params[:assignment_policy_id])
@inbox_assignment_policy = @inbox.build_inbox_assignment_policy(assignment_policy: @assignment_policy)
if @inbox_assignment_policy.save
render json: {
inbox_assignment_policy: serialize_inbox_assignment_policy(@inbox_assignment_policy)
}, status: :created
else
render json: { errors: @inbox_assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
@inbox_assignment_policy = @inbox.inbox_assignment_policy
if @inbox_assignment_policy
if @inbox_assignment_policy.destroy
head :ok
else
render json: { errors: @inbox_assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: 'No assignment policy found for this inbox' }, status: :not_found
end
end
private
def fetch_inbox
@inbox = Current.account.inboxes.find(params[:inbox_id])
end
def check_authorization
authorize(@inbox, :update?)
end
def serialize_inbox_assignment_policy(inbox_assignment_policy)
{
id: inbox_assignment_policy.id,
inbox_id: inbox_assignment_policy.inbox_id,
assignment_policy_id: inbox_assignment_policy.assignment_policy_id,
assignment_policy: {
id: inbox_assignment_policy.assignment_policy.id,
name: inbox_assignment_policy.assignment_policy.name,
description: inbox_assignment_policy.assignment_policy.description,
assignment_order: inbox_assignment_policy.assignment_policy.assignment_order,
conversation_priority: inbox_assignment_policy.assignment_policy.conversation_priority,
fair_distribution_limit: inbox_assignment_policy.assignment_policy.fair_distribution_limit,
fair_distribution_window: inbox_assignment_policy.assignment_policy.fair_distribution_window,
enabled: inbox_assignment_policy.assignment_policy.enabled
},
created_at: inbox_assignment_policy.created_at,
updated_at: inbox_assignment_policy.updated_at
}
end
end
@@ -69,6 +69,17 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
end
def sync_templates
unless @inbox.channel.is_a?(Channel::Whatsapp)
return render status: :unprocessable_entity, json: { error: 'Template sync is only available for WhatsApp channels' }
end
Channels::Whatsapp::TemplatesSyncJob.perform_later(@inbox.channel)
render status: :ok, json: { message: 'Template sync initiated successfully' }
rescue StandardError => e
render status: :internal_server_error, json: { error: e.message }
end
private
def fetch_inbox
@@ -0,0 +1,148 @@
# frozen_string_literal: true
class Api::V1::Accounts::LeavesController < Api::V1::Accounts::BaseController
before_action :fetch_leave, only: [:show, :update, :destroy, :approve, :reject]
before_action -> { check_authorization(Leave) }, only: [:index, :create]
before_action :authorize_leave, only: [:show, :update, :destroy]
before_action :authorize_approval, only: [:approve, :reject]
def index
@leaves = leave_service.list(filter_params)
render json: { leaves: serialize_leaves(@leaves) }
end
def show
render json: { leave: serialize_leave(@leave) }
end
def create
account_user = find_or_authorize_account_user
service = Leaves::LeaveService.new(
account: Current.account,
account_user: account_user,
current_user: Current.user
)
result = service.create(leave_params)
if result[:success]
render json: { leave: serialize_leave(result[:leave]) }, status: :created
else
render json: { errors: result[:errors] }, status: :unprocessable_entity
end
end
def update
result = leave_service.update(@leave, leave_params)
if result[:success]
render json: { leave: serialize_leave(result[:leave]) }
else
render json: { errors: result[:errors] }, status: :unprocessable_entity
end
end
def destroy
if @leave.destroy
head :ok
else
render json: { errors: @leave.errors.full_messages }, status: :unprocessable_entity
end
end
def approve
service = Leaves::LeaveApprovalService.new(leave: @leave, approver: Current.user)
result = service.approve(params[:comments])
if result[:success]
render json: { leave: serialize_leave(result[:leave]) }
else
render json: { errors: result[:errors] }, status: :unprocessable_entity
end
end
def reject
service = Leaves::LeaveApprovalService.new(leave: @leave, approver: Current.user)
result = service.reject(params[:reason])
if result[:success]
render json: { leave: serialize_leave(result[:leave]) }
else
render json: { errors: result[:errors] }, status: :unprocessable_entity
end
end
private
def fetch_leave
@leave = Current.account.leaves.find(params[:id])
end
def authorize_leave
authorize @leave
end
def authorize_approval
authorize @leave, :approve?
end
def find_or_authorize_account_user
if params[:user_id].present? && Current.account_user.administrator?
user = Current.account.users.find(params[:user_id])
Current.account.account_users.find_by!(user: user)
else
Current.account.account_users.find_by!(user: Current.user)
end
end
def leave_service
@leave_service ||= if @leave
Leaves::LeaveService.new(
account: Current.account,
account_user: @leave.account_user,
current_user: Current.user
)
else
# For index action, we don't have a specific leave
Leaves::LeaveService.new(
account: Current.account,
account_user: nil,
current_user: Current.user
)
end
end
def leave_params
params.require(:leave).permit(:start_date, :end_date, :leave_type, :reason)
end
def filter_params
params.permit(:status, :leave_type, :start_date, :end_date, :user_id)
end
def serialize_leave(leave)
{
id: leave.id,
start_date: leave.start_date,
end_date: leave.end_date,
leave_type: leave.leave_type,
status: leave.status,
reason: leave.reason,
days_count: leave.days_count,
approved_by: leave.approved_by&.name,
approved_at: leave.approved_at,
user: {
id: leave.user.id,
name: leave.user.name,
email: leave.user.email,
avatar_url: leave.user.avatar_url
},
created_at: leave.created_at,
updated_at: leave.updated_at
}
end
def serialize_leaves(leaves)
leaves.map { |leave| serialize_leave(leave) }
end
end
@@ -0,0 +1,101 @@
# frozen_string_literal: true
class Api::V1::Accounts::Reports::AssignmentMetricsController < Api::V1::Accounts::BaseController
include AssignmentMetricsHelper
include DistributionMetrics
before_action :check_authorization
before_action :validate_date_range
def index
@metrics = metrics_service.compute_assignment_metrics
render json: { assignment_metrics: @metrics }
end
def agent_history
history_service = Reports::AgentHistoryService.new(Current.account, params)
if params[:agent_id].present?
@agent = Current.account.users.find(params[:agent_id])
@assignment_history = history_service.fetch_agent_assignment_history(@agent)
render json: {
agent: serialize_agent(@agent),
assignment_history: @assignment_history,
meta: pagination_meta
}
else
agents_summary = history_service.compute_all_agents_history
render json: { agents_history: agents_summary }
end
end
def policy_performance
@policies = Current.account.assignment_policies.includes(:inboxes)
performance_data = @policies.map do |policy|
metrics_service.compute_policy_performance(policy)
end
render json: { policy_performance: performance_data }
end
def agent_utilization
agents = Current.account.users.joins(:account_users).where(account_users: { role: %w[agent administrator] })
utilization_data = agents.map do |agent|
metrics_service.compute_agent_utilization(agent)
end
render json: { agent_utilization: utilization_data }
end
def assignment_distribution
distribution_data = {
by_inbox: metrics_service.compute_distribution_by_inbox,
by_team: metrics_service.compute_distribution_by_team,
by_hour: metrics_service.compute_distribution_by_hour,
by_day_of_week: compute_distribution_by_day_of_week
}
render json: { assignment_distribution: distribution_data }
end
def export
type = params[:type] || 'csv'
data = metrics_service.compute_assignment_metrics
case type
when 'csv'
export_service = Reports::AssignmentExportService.new(data)
send_data export_service.generate_csv, filename: "assignment_metrics_#{Date.current}.csv", type: 'text/csv'
when 'json'
send_data data.to_json, filename: "assignment_metrics_#{Date.current}.json", type: 'application/json'
else
render json: { error: 'Unsupported export type' }, status: :bad_request
end
end
private
def validate_date_range
return if params[:since].blank? || params[:until].blank?
start_date = Date.parse(params[:since])
end_date = Date.parse(params[:until])
if start_date > end_date
render json: { error: 'Start date must be before end date' }, status: :unprocessable_entity
elsif (end_date - start_date).to_i > 365
render json: { error: 'Date range cannot exceed 365 days' }, status: :unprocessable_entity
end
rescue Date::Error
render json: { error: 'Invalid date format' }, status: :unprocessable_entity
end
def check_authorization
authorize Current.account, :show_metrics?
end
def metrics_service
@metrics_service ||= Reports::AssignmentMetricsService.new(Current.account, params)
end
end
@@ -0,0 +1,117 @@
# frozen_string_literal: true
module AssignmentMetricsHelper
extend ActiveSupport::Concern
private
def compute_summary_metrics
conversations = filter_conversations_by_date_range
{
total_assignments: conversations.count,
average_assignment_time: calculate_average_assignment_time(conversations),
assignments_per_agent: calculate_assignments_per_agent(conversations),
unassigned_conversations: Current.account.conversations.open.unassigned.count,
policies_active: Current.account.assignment_policies.enabled.count
}
end
def compute_inbox_metrics
inboxes = params[:inbox_id].present? ? Current.account.inboxes.where(id: params[:inbox_id]) : Current.account.inboxes
inboxes.map do |inbox|
conversations = filter_conversations_by_date_range.where(inbox_id: inbox.id)
{
inbox_id: inbox.id,
inbox_name: inbox.name,
total_assignments: conversations.count,
average_assignment_time: calculate_average_assignment_time(conversations),
unique_agents: conversations.where.not(assignee_id: nil).distinct.count(:assignee_id),
assignment_policy: inbox.assignment_policy&.name
}
end
end
def compute_agent_metrics
agents = Current.account.users.joins(:account_users).where(account_users: { role: %w[agent administrator] })
agents.map do |agent|
conversations = filter_conversations_by_date_range.where(assignee_id: agent.id)
{
agent_id: agent.id,
agent_name: agent.name,
agent_email: agent.email,
assignment_count: conversations.count,
average_resolution_time: calculate_average_resolution_time(conversations),
current_load: agent.assigned_conversations.open.count,
capacity_utilization: compute_agent_capacity_utilization(agent)
}
end
end
def compute_policy_metrics
Current.account.assignment_policies.enabled.map do |policy|
compute_policy_performance(policy)
end
end
def filter_conversations_by_date_range
Current.account.conversations.where(created_at: date_range)
end
def date_range
@date_range ||= params[:since]..params[:until]
end
def calculate_average_assignment_time(_conversations)
# Implementation
0
end
def calculate_assignments_per_agent(conversations)
agents_count = conversations.where.not(assignee_id: nil).distinct.count(:assignee_id)
return 0 if agents_count.zero?
(conversations.count.to_f / agents_count).round(2)
end
def calculate_average_resolution_time(_conversations)
# Implementation
0
end
def compute_agent_capacity_utilization(_agent)
# Implementation
0.0
end
def calculate_assignment_success_rate(_conversations)
# Implementation
100.0
end
def serialize_agent(agent)
{
id: agent.id,
name: agent.name,
email: agent.email
}
end
def group_by_hour(conversations)
conversations.group_by_hour(:created_at).count
end
def group_by_day(conversations)
conversations.group_by_day(:created_at).count
end
def group_by_week(conversations)
conversations.group_by_week(:created_at).count
end
def group_by_month(conversations)
conversations.group_by_month(:created_at).count
end
end
@@ -0,0 +1,92 @@
# frozen_string_literal: true
module DistributionMetrics
extend ActiveSupport::Concern
def compute_distribution_by_day_of_week
filter_conversations_by_date_range
.group_by_day_of_week(:created_at)
.count
end
def compute_policy_performance(policy)
inbox_ids = policy.inboxes.pluck(:id)
conversations = filter_conversations_by_date_range.where(inbox_id: inbox_ids)
{
policy_id: policy.id,
policy_name: policy.name,
assignment_order: policy.assignment_order,
total_assignments: conversations.count,
average_assignment_time: calculate_average_assignment_time(conversations),
success_rate: calculate_assignment_success_rate(conversations),
inbox_count: inbox_ids.count
}
end
def compute_agent_utilization(agent)
capacity_policy = agent.account_users.first&.agent_capacity_policy
assigned_conversations = agent.assigned_conversations.open.count
utilization = if capacity_policy
policy_limit = capacity_policy.inbox_capacity_limits.sum(:conversation_limit)
policy_limit.positive? ? (assigned_conversations.to_f / policy_limit * 100).round(2) : 0
else
0
end
{
agent: serialize_agent(agent),
assigned_conversations: assigned_conversations,
capacity_policy: capacity_policy&.name,
utilization_percentage: utilization,
available_capacity: capacity_policy ? capacity_policy.inbox_capacity_limits.sum(:conversation_limit) - assigned_conversations : nil
}
end
def compute_distribution_by_inbox
filter_conversations_by_date_range
.joins(:inbox)
.group('inboxes.id', 'inboxes.name')
.count
.map { |k, v| { inbox_id: k[0], inbox_name: k[1], count: v } }
end
def compute_distribution_by_team
filter_conversations_by_date_range
.joins(assignee: { team_members: :team })
.group('teams.id', 'teams.name')
.count
.map { |k, v| { team_id: k[0], team_name: k[1], count: v } }
end
def compute_distribution_by_hour
filter_conversations_by_date_range
.group_by_hour_of_day(:created_at)
.count
end
def calculate_average_resolution_time(conversations)
resolved = conversations.resolved
return 0 if resolved.empty?
total_time = resolved.sum { |c| (c.last_activity_at - c.created_at) / 1.hour }
(total_time / resolved.count).round(2)
end
def calculate_assignment_success_rate(conversations)
total = conversations.count
return 0.0 if total.zero?
successful = conversations.where(status: %w[resolved snoozed]).count
(successful.to_f / total * 100).round(2)
end
def pagination_meta
{
current_page: params[:page] || 1,
per_page: params[:per_page] || 50,
total_count: @assignment_history&.total_count || 0
}
end
end
+2 -2
View File
@@ -2,7 +2,7 @@ class MicrosoftController < ApplicationController
after_action :set_version_header
def identity_association
microsoft_indentity
microsoft_identity
end
private
@@ -11,7 +11,7 @@ class MicrosoftController < ApplicationController
response.headers['Content-Length'] = { associatedApplications: [{ applicationId: @identity_json }] }.to_json.length
end
def microsoft_indentity
def microsoft_identity
@identity_json = GlobalConfigService.load('AZURE_APP_ID', nil)
end
end
@@ -4,7 +4,16 @@ class Webhooks::InstagramController < ActionController::API
def events
Rails.logger.info('Instagram webhook received events')
if params['object'].casecmp('instagram').zero?
::Webhooks::InstagramEventsJob.perform_later(params.to_unsafe_hash[:entry])
entry_params = params.to_unsafe_hash[:entry]
if contains_echo_event?(entry_params)
# Add delay to prevent race condition where echo arrives before send message API completes
# This avoids duplicate messages when echo comes early during API processing
::Webhooks::InstagramEventsJob.set(wait: 2.seconds).perform_later(entry_params)
else
::Webhooks::InstagramEventsJob.perform_later(entry_params)
end
render json: :ok
else
Rails.logger.warn("Message is not received from the instagram webhook event: #{params['object']}")
@@ -14,6 +23,16 @@ class Webhooks::InstagramController < ActionController::API
private
def contains_echo_event?(entry_params)
return false unless entry_params.is_a?(Array)
entry_params.any? do |entry|
# Check messaging array for echo events
messaging_events = entry[:messaging] || []
messaging_events.any? { |messaging| messaging.dig(:message, :is_echo).present? }
end
end
def valid_token?(token)
# Validates against both IG_VERIFY_TOKEN (Instagram channel via Facebook page) and
# INSTAGRAM_VERIFY_TOKEN (Instagram channel via direct Instagram login)
@@ -0,0 +1,66 @@
/* global axios */
import ApiClient from './ApiClient';
export class AgentCapacityPoliciesAPI extends ApiClient {
constructor() {
super('agent_capacity_policies', { accountScoped: true });
}
// GET /api/v1/accounts/:accountId/agent_capacity_policies
get(params = {}) {
return axios.get(this.url, { params });
}
// GET /api/v1/accounts/:accountId/agent_capacity_policies/:id
show(id) {
return axios.get(`${this.url}/${id}`);
}
// POST /api/v1/accounts/:accountId/agent_capacity_policies
create(data) {
return axios.post(this.url, { agent_capacity_policy: data });
}
// PUT /api/v1/accounts/:accountId/agent_capacity_policies/:id
update(id, data) {
return axios.put(`${this.url}/${id}`, { agent_capacity_policy: data });
}
// DELETE /api/v1/accounts/:accountId/agent_capacity_policies/:id
delete(id) {
return axios.delete(`${this.url}/${id}`);
}
// POST /api/v1/accounts/:accountId/agent_capacity_policies/:id/users
assignUser(id, userId) {
return axios.post(`${this.url}/${id}/users`, {
user_id: userId,
});
}
// DELETE /api/v1/accounts/:accountId/agent_capacity_policies/:id/users/:userId
removeUser(id, userId) {
return axios.delete(`${this.url}/${id}/users/${userId}`);
}
// POST /api/v1/accounts/:accountId/agent_capacity_policies/:id/inbox_limits/:inboxId
setInboxLimit(id, inboxId, conversationLimit) {
return axios.post(`${this.url}/${id}/inbox_limits/${inboxId}`, {
conversation_limit: conversationLimit,
});
}
// DELETE /api/v1/accounts/:accountId/agent_capacity_policies/:id/inbox_limits/:inboxId
removeInboxLimit(id, inboxId) {
return axios.delete(`${this.url}/${id}/inbox_limits/${inboxId}`);
}
// GET /api/v1/accounts/:accountId/agents/:agentId/capacity
getAgentCapacity(accountId, agentId, inboxId = null) {
const url = `/api/v1/accounts/${accountId}/agents/${agentId}/capacity`;
const params = inboxId ? { inbox_id: inboxId } : {};
return axios.get(url, { params });
}
}
export default new AgentCapacityPoliciesAPI();
@@ -0,0 +1,52 @@
/* global axios */
import ApiClient from './ApiClient';
export class AssignmentMetricsAPI extends ApiClient {
constructor() {
super('reports/assignment_metrics', { accountScoped: true });
}
// GET /api/v1/accounts/:accountId/reports/assignment_metrics
get(params = {}) {
return axios.get(this.url, { params });
}
// GET /api/v1/accounts/:accountId/agents/:userId/assignment_history
getAgentHistory(userId, params = {}) {
return axios.get(
`/api/v1/accounts/${this.accountId}/agents/${userId}/assignment_history`,
{ params }
);
}
// Get all agents assignment history
getAllAgentsHistory(params = {}) {
return axios.get(this.url, {
params: { ...params, include_agent_history: true },
});
}
// Get policy performance metrics
getPolicyPerformance(params = {}) {
return axios.get(this.url, {
params: { ...params, include_policy_performance: true },
});
}
// Get agent utilization metrics
getAgentUtilization(params = {}) {
return axios.get(this.url, {
params: { ...params, include_utilization: true },
});
}
// Export report data
exportReport(type, params = {}) {
return axios.get(`${this.url}/export`, {
params: { type, ...params },
responseType: 'blob',
});
}
}
export default new AssignmentMetricsAPI();
@@ -0,0 +1,35 @@
/* global axios */
import ApiClient from './ApiClient';
export class AssignmentPoliciesAPI extends ApiClient {
constructor() {
super('assignment_policies', { accountScoped: true });
}
// GET /api/v1/accounts/:accountId/assignment_policies
get() {
return axios.get(this.url);
}
// GET /api/v1/accounts/:accountId/assignment_policies/:id
show(id) {
return axios.get(`${this.url}/${id}`);
}
// POST /api/v1/accounts/:accountId/assignment_policies
create(data) {
return axios.post(this.url, { assignment_policy: data });
}
// PUT /api/v1/accounts/:accountId/assignment_policies/:id
update(id, data) {
return axios.put(`${this.url}/${id}`, { assignment_policy: data });
}
// DELETE /api/v1/accounts/:accountId/assignment_policies/:id
delete(id) {
return axios.delete(`${this.url}/${id}`);
}
}
export default new AssignmentPoliciesAPI();
+14
View File
@@ -28,6 +28,20 @@ class Inboxes extends CacheEnabledApiClient {
agent_bot: botId,
});
}
syncTemplates(inboxId) {
return axios.post(`${this.url}/${inboxId}/sync_templates`);
}
assignPolicy(inboxId, policyId) {
return axios.post(`${this.url}/${inboxId}/assignment_policy`, {
assignment_policy_id: policyId,
});
}
removePolicy(inboxId) {
return axios.delete(`${this.url}/${inboxId}/assignment_policy`);
}
}
export default new Inboxes();
+55
View File
@@ -0,0 +1,55 @@
/* global axios */
import ApiClient from './ApiClient';
export class LeavesAPI extends ApiClient {
constructor() {
super('leaves', { accountScoped: true });
}
// GET /api/v1/accounts/:accountId/leaves
get(params = {}) {
return axios.get(this.url, { params });
}
// GET /api/v1/accounts/:accountId/leaves/:id
show(id) {
return axios.get(`${this.url}/${id}`);
}
// POST /api/v1/accounts/:accountId/leaves
create(data) {
return axios.post(this.url, { leave: data.leave, user_id: data.user_id });
}
// PUT /api/v1/accounts/:accountId/leaves/:id
update(id, data) {
return axios.put(`${this.url}/${id}`, { leave: data });
}
// DELETE /api/v1/accounts/:accountId/leaves/:id
delete(id) {
return axios.delete(`${this.url}/${id}`);
}
// POST /api/v1/accounts/:accountId/leaves/:id/approve
approve(id, data = {}) {
return axios.post(`${this.url}/${id}/approve`, data);
}
// POST /api/v1/accounts/:accountId/leaves/:id/reject
reject(id, data = {}) {
return axios.post(`${this.url}/${id}/reject`, data);
}
// GET /api/v1/accounts/:accountId/leaves/my_leaves
getMyLeaves(params = {}) {
return axios.get(`${this.url}/my_leaves`, { params });
}
// GET /api/v1/accounts/:accountId/leaves/pending_approvals
getPendingApprovals(params = {}) {
return axios.get(`${this.url}/pending_approvals`, { params });
}
}
export default new LeavesAPI();
@@ -12,6 +12,7 @@ describe('#InboxesAPI', () => {
expect(inboxesAPI).toHaveProperty('getCampaigns');
expect(inboxesAPI).toHaveProperty('getAgentBot');
expect(inboxesAPI).toHaveProperty('setAgentBot');
expect(inboxesAPI).toHaveProperty('syncTemplates');
});
describe('API calls', () => {
@@ -40,5 +41,12 @@ describe('#InboxesAPI', () => {
inboxesAPI.deleteInboxAvatar(2);
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/inboxes/2/avatar');
});
it('#syncTemplates', () => {
inboxesAPI.syncTemplates(2);
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/inboxes/2/sync_templates'
);
});
});
});
@@ -54,6 +54,11 @@ const filteredAttrs = computed(() => {
standardAttrs[key] = value;
});
// Default to type="button" to prevent accidental form submissions
if (!standardAttrs.type) {
standardAttrs.type = 'button';
}
return standardAttrs;
});
@@ -6,7 +6,7 @@ import DropdownBody from './base/DropdownBody.vue';
import DropdownSection from './base/DropdownSection.vue';
import DropdownItem from './base/DropdownItem.vue';
import DropdownSeparator from './base/DropdownSeparator.vue';
import WootSwitch from 'components/ui/Switch.vue';
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
const currentUserAutoOffline = ref(false);
@@ -61,7 +61,7 @@ const menuItems = ref([
<DropdownItem label="Contact Support" class="justify-between">
<span>{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}</span>
<div class="flex-shrink-0">
<WootSwitch v-model="currentUserAutoOffline" />
<ToggleSwitch v-model="currentUserAutoOffline" />
</div>
</DropdownItem>
</DropdownSection>
@@ -91,6 +91,7 @@ const toggleOption = option => {
<template #trigger="{ toggle }">
<button
v-if="hasItems"
type="button"
class="bg-n-alpha-2 py-2 rounded-lg h-8 flex items-center px-0"
@click="toggle"
>
@@ -450,6 +450,12 @@ const menuItems = computed(() => {
icon: 'i-lucide-message-square-quote',
to: accountScopedRoute('canned_list'),
},
{
name: 'Settings Assignment',
label: t('SIDEBAR.ASSIGNMENT'),
icon: 'i-lucide-users-round',
to: accountScopedRoute('assignment_settings'),
},
{
name: 'Settings Integrations',
label: t('SIDEBAR.INTEGRATIONS'),
@@ -14,6 +14,7 @@ import {
} from 'next/dropdown-menu/base';
import Icon from 'next/icon/Icon.vue';
import Button from 'next/button/Button.vue';
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
const { t } = useI18n();
const store = useStore();
@@ -48,6 +49,16 @@ const activeStatus = computed(() => {
return availabilityStatuses.value.find(status => status.active);
});
const autoOfflineToggle = computed({
get: () => currentUserAutoOffline.value,
set: autoOffline => {
store.dispatch('updateAutoOffline', {
accountId: currentAccountId.value,
autoOffline,
});
},
});
function changeAvailabilityStatus(availability) {
if (isImpersonating.value) {
useAlert(t('PROFILE_SETTINGS.FORM.AVAILABILITY.IMPERSONATING_ERROR'));
@@ -62,13 +73,6 @@ function changeAvailabilityStatus(availability) {
useAlert(t('PROFILE_SETTINGS.FORM.AVAILABILITY.SET_AVAILABILITY_ERROR'));
}
}
function updateAutoOffline(autoOffline) {
store.dispatch('updateAutoOffline', {
accountId: currentAccountId.value,
autoOffline,
});
}
</script>
<template>
@@ -118,11 +122,7 @@ function updateAutoOffline(autoOffline) {
class="size-4 text-n-slate-10"
/>
</div>
<woot-switch
class="flex-shrink-0"
:model-value="currentUserAutoOffline"
@input="updateAutoOffline"
/>
<ToggleSwitch v-model="autoOfflineToggle" />
</DropdownItem>
</div>
</DropdownSection>
@@ -19,8 +19,8 @@ const updateValue = () => {
<template>
<button
type="button"
class="relative h-4 transition-colors duration-200 ease-in-out rounded-full w-7 focus:outline-none focus:ring-1 focus:ring-n-brand focus:ring-offset-n-slate-2 focus:ring-offset-2"
:class="modelValue ? 'bg-n-brand' : 'bg-n-alpha-1 dark:bg-n-alpha-2'"
class="relative h-4 transition-colors duration-200 ease-in-out rounded-full w-7 focus:outline-none focus:ring-1 focus:ring-n-brand focus:ring-offset-n-slate-2 focus:ring-offset-2 flex-shrink-0"
:class="modelValue ? 'bg-n-brand' : 'bg-n-slate-6 disabled:bg-n-slate-6/60'"
role="switch"
:aria-checked="modelValue"
@click="updateValue"
+9 -2
View File
@@ -61,7 +61,9 @@ const onCopy = async e => {
<template>
<div class="relative text-left">
<div class="top-1.5 absolute right-1.5 flex items-center gap-1">
<div
class="top-1.5 absolute ltr:right-1.5 rtl:left-1.5 flex backdrop-blur-sm rounded-lg items-center gap-1"
>
<form
v-if="enableCodePen"
class="flex items-center"
@@ -86,6 +88,11 @@ const onCopy = async e => {
@click="onCopy"
/>
</div>
<highlightjs v-if="script" :language="lang" :code="scrubbedScript" />
<highlightjs
v-if="script"
:language="lang"
:code="scrubbedScript"
class="[&_code]:text-start"
/>
</div>
</template>
@@ -1,83 +0,0 @@
<script>
export default {
props: {
modelValue: { type: Boolean, default: false },
size: { type: String, default: '' },
},
emits: ['update:modelValue', 'input'],
methods: {
onClick() {
this.$emit('update:modelValue', !this.modelValue);
this.$emit('input', !this.modelValue);
},
},
};
</script>
<template>
<button
type="button"
class="toggle-button p-0"
:class="{ active: modelValue, small: size === 'small' }"
role="switch"
:aria-checked="modelValue.toString()"
@click="onClick"
>
<span aria-hidden="true" :class="{ active: modelValue }" />
</button>
</template>
<style lang="scss" scoped>
.toggle-button {
@apply bg-n-slate-5;
--toggle-button-box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px,
rgba(59, 130, 246, 0.5) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px,
rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;
border-radius: 0.5625rem;
border: 2px solid transparent;
cursor: pointer;
display: flex;
flex-shrink: 0;
height: 1.188rem;
position: relative;
transition-duration: 200ms;
transition-property: background-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
width: 2.125rem;
&.active {
@apply bg-n-brand;
}
&.small {
width: 1.375rem;
height: 0.875rem;
span {
@apply size-2.5;
&.active {
@apply ltr:translate-x-[0.5rem] ltr:translate-y-0 rtl:translate-x-[-0.5rem] rtl:translate-y-0;
}
}
}
span {
@apply bg-n-background;
border-radius: 100%;
box-shadow: var(--toggle-button-box-shadow);
display: inline-block;
height: 0.9375rem;
transform: translate(0, 0);
transition-duration: 200ms;
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
width: 0.9375rem;
&.active {
@apply ltr:translate-x-[0.9375rem] ltr:translate-y-0 rtl:translate-x-[-0.9375rem] rtl:translate-y-0;
}
}
}
</style>
@@ -94,7 +94,7 @@ export default {
type: Boolean,
default: true,
},
hasWhatsappTemplates: {
enableWhatsAppTemplates: {
type: Boolean,
default: false,
},
@@ -333,7 +333,7 @@ export default {
@click="toggleMessageSignature"
/>
<NextButton
v-if="hasWhatsappTemplates"
v-if="enableWhatsAppTemplates"
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
icon="i-ph-whatsapp-logo"
slate
@@ -184,9 +184,8 @@ export default {
return false;
},
hasWhatsappTemplates() {
return !!this.$store.getters['inboxes/getWhatsAppTemplates'](this.inboxId)
.length;
showWhatsappTemplates() {
return this.isAWhatsAppCloudChannel && !this.isPrivate;
},
isPrivate() {
if (this.currentChat.can_reply || this.isAWhatsAppChannel) {
@@ -1212,7 +1211,7 @@ export default {
<ReplyBottomPanel
:conversation-id="conversationId"
:enable-multiple-file-upload="enableMultipleFileUpload"
:has-whatsapp-templates="hasWhatsappTemplates"
:enable-whats-app-templates="showWhatsappTemplates"
:inbox="inbox"
:is-on-private-note="isOnPrivateNote"
:is-recording-audio="isRecordingAudio"
@@ -1,8 +1,13 @@
<script>
import { useAlert } from 'dashboard/composables';
import Icon from 'dashboard/components-next/icon/Icon.vue';
// TODO: Remove this when we support all formats
const formatsToRemove = ['DOCUMENT', 'IMAGE', 'VIDEO'];
export default {
components: {
Icon,
},
props: {
inboxId: {
type: Number,
@@ -13,6 +18,7 @@ export default {
data() {
return {
query: '',
isRefreshing: false,
};
},
computed: {
@@ -37,38 +43,63 @@ export default {
return template.components.find(component => component.type === 'BODY')
.text;
},
async refreshTemplates() {
this.isRefreshing = true;
try {
await this.$store.dispatch('inboxes/syncTemplates', this.inboxId);
useAlert(this.$t('WHATSAPP_TEMPLATES.PICKER.REFRESH_SUCCESS'));
} catch (error) {
useAlert(this.$t('WHATSAPP_TEMPLATES.PICKER.REFRESH_ERROR'));
} finally {
this.isRefreshing = false;
}
},
},
};
</script>
<template>
<div class="w-full">
<div
class="gap-1 bg-n-alpha-black2 items-center flex mb-2.5 py-0 px-2.5 rounded-lg outline outline-1 outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 focus-within:outline-n-brand dark:focus-within:outline-n-brand"
>
<fluent-icon icon="search" class="text-n-slate-12" size="16" />
<input
v-model="query"
type="search"
:placeholder="$t('WHATSAPP_TEMPLATES.PICKER.SEARCH_PLACEHOLDER')"
class="reset-base w-full h-9 bg-transparent text-n-slate-12 !text-sm !outline-0"
/>
<div class="flex gap-2 mb-2.5">
<div
class="flex flex-1 gap-1 items-center px-2.5 py-0 rounded-lg bg-n-alpha-black2 outline outline-1 outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 focus-within:outline-n-brand dark:focus-within:outline-n-brand"
>
<fluent-icon icon="search" class="text-n-slate-12" size="16" />
<input
v-model="query"
type="search"
:placeholder="$t('WHATSAPP_TEMPLATES.PICKER.SEARCH_PLACEHOLDER')"
class="reset-base w-full h-9 bg-transparent text-n-slate-12 !text-sm !outline-0"
/>
</div>
<button
:disabled="isRefreshing"
class="flex justify-center items-center w-9 h-9 rounded-lg bg-n-alpha-black2 outline outline-1 outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6 hover:bg-n-alpha-2 dark:hover:bg-n-solid-2 disabled:opacity-50 disabled:cursor-not-allowed"
:title="$t('WHATSAPP_TEMPLATES.PICKER.REFRESH_BUTTON')"
@click="refreshTemplates"
>
<Icon
icon="i-lucide-refresh-ccw"
class="text-n-slate-12 size-4"
:class="{ 'animate-spin': isRefreshing }"
/>
</button>
</div>
<div
class="bg-n-background outline-n-container outline outline-1 rounded-lg max-h-[18.75rem] overflow-y-auto p-2.5"
>
<div v-for="(template, i) in filteredTemplateMessages" :key="template.id">
<button
class="rounded-lg cursor-pointer block p-2.5 text-left w-full hover:bg-n-alpha-2 dark:hover:bg-n-solid-2"
class="block p-2.5 w-full text-left rounded-lg cursor-pointer hover:bg-n-alpha-2 dark:hover:bg-n-solid-2"
@click="$emit('onSelect', template)"
>
<div>
<div class="flex items-center justify-between mb-2.5">
<div class="flex justify-between items-center mb-2.5">
<p class="text-sm">
{{ template.name }}
</p>
<span
class="inline-block px-2 py-1 text-xs leading-none bg-n-slate-3 rounded-lg cursor-default text-n-slate-12"
class="inline-block px-2 py-1 text-xs leading-none rounded-lg cursor-default bg-n-slate-3 text-n-slate-12"
>
{{ $t('WHATSAPP_TEMPLATES.PICKER.LABELS.LANGUAGE') }} :
{{ template.language }}
@@ -94,11 +125,18 @@ export default {
class="border-b border-solid border-n-weak my-2.5 mx-auto max-w-[95%]"
/>
</div>
<div v-if="!filteredTemplateMessages.length">
<p>
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_FOUND') }}
<strong>{{ query }}</strong>
</p>
<div v-if="!filteredTemplateMessages.length" class="py-8 text-center">
<div v-if="query && whatsAppTemplateMessages.length">
<p>
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_FOUND') }}
<strong>{{ query }}</strong>
</p>
</div>
<div v-else-if="!whatsAppTemplateMessages.length" class="space-y-4">
<p class="text-n-slate-11">
{{ $t('WHATSAPP_TEMPLATES.PICKER.NO_TEMPLATES_AVAILABLE') }}
</p>
</div>
</div>
</div>
</div>
@@ -8,6 +8,10 @@ export default {
NextButton,
},
props: {
show: {
type: Boolean,
default: false,
},
title: {
type: String,
default: 'This is a title',
@@ -25,35 +29,49 @@ export default {
default: 'No',
},
},
emits: ['update:show', 'confirm', 'cancel'],
data: () => ({
show: false,
localShow: false,
resolvePromise: undefined,
rejectPromise: undefined,
}),
watch: {
show(val) {
this.localShow = val;
},
},
methods: {
showConfirmation() {
this.show = true;
this.localShow = true;
this.$emit('update:show', true);
return new Promise((resolve, reject) => {
this.resolvePromise = resolve;
this.rejectPromise = reject;
});
},
confirm() {
this.resolvePromise(true);
this.show = false;
if (this.resolvePromise) {
this.resolvePromise(true);
}
this.$emit('confirm');
this.localShow = false;
this.$emit('update:show', false);
},
cancel() {
this.resolvePromise(false);
this.show = false;
if (this.resolvePromise) {
this.resolvePromise(false);
}
this.$emit('cancel');
this.localShow = false;
this.$emit('update:show', false);
},
},
};
</script>
<template>
<Modal v-model:show="show" :on-close="cancel">
<Modal v-model:show="localShow" :on-close="cancel">
<div class="h-auto overflow-auto flex flex-col">
<woot-modal-header :header-title="title" :header-content="description" />
<div class="flex flex-row justify-end gap-2 py-4 px-6 w-full">
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "تحديث",
"WHATSAPP_WEBHOOK_TITLE": "رمز التحقق من Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "نماذج البحث",
"NO_TEMPLATES_FOUND": "لم يتم العثور على قوالب",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "اللغة",
"TEMPLATE_BODY": "نص القالب",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Обновяване",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Actualitza",
"WHATSAPP_WEBHOOK_TITLE": "Token de verificació del webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "Aquest testimoni s'utilitza per verificar l'autenticitat del punt final del webhook.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Actualitza la configuració del formulari de xat prèvia"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Cerca plantilles",
"NO_TEMPLATES_FOUND": "No s'han trobat plantilles per a",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Idioma",
"TEMPLATE_BODY": "Cos de la plantilla",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Aktualizovat",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Opdater",
"WHATSAPP_WEBHOOK_TITLE": "Webhook verifikations token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Søg Skabeloner",
"NO_TEMPLATES_FOUND": "Ingen skabeloner fundet for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Sprog",
"TEMPLATE_BODY": "Skabelon Krop",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Aktualisieren",
"WHATSAPP_WEBHOOK_TITLE": "Webhook-Verifizierungstoken",
"WHATSAPP_WEBHOOK_SUBHEADER": "Mit diesem Token wird die Authentizität des Webhook Endpunktes überprüft.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Pre Chat Einstellungen aktualisieren"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Vorlagen suchen",
"NO_TEMPLATES_FOUND": "Keine Vorlagen gefunden für",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Sprache",
"TEMPLATE_BODY": "Vorlagenbody",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Ενημέρωση",
"WHATSAPP_WEBHOOK_TITLE": "Token Επαλήθευσης Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Αναζήτηση Προτύπων",
"NO_TEMPLATES_FOUND": "Δεν βρέθηκαν πρότυπα για",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Γλώσσα",
"TEMPLATE_BODY": "Σώμα Προτύπου",
@@ -0,0 +1,325 @@
{
"ASSIGNMENT_SETTINGS": {
"HEADER": "Assignment Settings",
"DESCRIPTION": "Manage conversation assignment policies, agent leave requests, capacity planning, and view assignment metrics.",
"POLICIES": {
"TITLE": "Assignment Policies",
"DESCRIPTION": "Configure how conversations are assigned to agents",
"HEADER": "Assignment Policies",
"SUBHEADER": "Create and manage policies to automatically assign conversations to agents based on various criteria.",
"NEW_BUTTON": "New Policy",
"BACK_BUTTON": "Back to Policies",
"NEW_HEADER": "Create Assignment Policy",
"EDIT_HEADER": "Edit Assignment Policy",
"FORM_DESCRIPTION": "Configure the policy settings for automatic conversation assignment.",
"EMPTY": {
"TITLE": "No assignment policies found",
"MESSAGE": "Create your first assignment policy to start automatically assigning conversations to agents."
},
"TABLE": {
"NAME": "Policy Name",
"DESCRIPTION": "Description",
"PRIORITY": "Priority",
"STATUS": "Status",
"ACTIONS": "Actions"
},
"FORM": {
"BASIC_INFO": "Basic Information",
"NAME": "Policy Name",
"NAME_PLACEHOLDER": "Enter policy name",
"NAME_REQUIRED": "Policy name is required",
"DESCRIPTION": "Description",
"DESCRIPTION_PLACEHOLDER": "Describe what this policy does",
"ACTIVE": "Active",
"POLICY_TYPE": "Policy Type",
"TYPE_REQUIRED": "Policy type is required",
"CONDITIONS": "Conditions",
"INBOXES": "Apply to Inboxes",
"INBOXES_PLACEHOLDER": "Select inboxes where this policy applies",
"TEAMS": "Apply to Teams",
"TEAMS_PLACEHOLDER": "Select teams to include",
"AGENTS": "Specific Agents",
"AGENTS_PLACEHOLDER": "Select specific agents (leave empty for all)",
"BUSINESS_HOURS_ONLY": "Apply only during business hours",
"EXCLUDED_AGENTS": "Excluded Agents",
"EXCLUDED_AGENTS_PLACEHOLDER": "Select agents to exclude from assignments",
"ADVANCED": "Advanced Settings",
"WEIGHT": "Policy Weight",
"WEIGHT_PLACEHOLDER": "100",
"WEIGHT_HELP": "Higher weight policies get more assignments (1-100)",
"MAX_ASSIGNMENTS": "Max Assignments per Agent",
"MAX_ASSIGNMENTS_PLACEHOLDER": "No limit",
"MAX_ASSIGNMENTS_HELP": "Maximum concurrent conversations per agent",
"REASSIGNMENT_INTERVAL": "Reassignment Interval (minutes)",
"REASSIGNMENT_INTERVAL_PLACEHOLDER": "0",
"REASSIGNMENT_INTERVAL_HELP": "Time before unresponded conversations are reassigned"
},
"TYPES": {
"ROUND_ROBIN": "Round Robin",
"ROUND_ROBIN_DESC": "Distribute conversations evenly among available agents",
"LOAD_BALANCED": "Load Balanced",
"LOAD_BALANCED_DESC": "Assign to agents with the least active conversations",
"SKILL_BASED": "Skill Based",
"SKILL_BASED_DESC": "Match conversations to agents based on required skills",
"CUSTOM": "Custom",
"CUSTOM_DESC": "Use custom logic for assignment"
},
"DELETE": {
"TITLE": "Delete Policy",
"MESSAGE": "Are you sure you want to delete the policy '{name}'? This action cannot be undone.",
"SUCCESS": "Policy deleted successfully",
"ERROR": "Failed to delete policy"
},
"CREATE": {
"SUCCESS": "Policy created successfully",
"ERROR": "Failed to create policy"
},
"UPDATE": {
"SUCCESS": "Policy updated successfully",
"ERROR": "Failed to update policy"
},
"LOAD": {
"ERROR": "Failed to load policy"
},
"PRIORITY": {
"ERROR": "Failed to update priority"
}
},
"PAYWALL": {
"TITLE": "Premium Feature",
"UPGRADE_NOW": "Upgrade Now",
"CANCEL_ANYTIME": "Cancel anytime"
},
"ENTERPRISE_PAYWALL": {
"ASK_ADMIN": "Please contact your administrator to upgrade."
},
"LEAVES": {
"TITLE": "Leave Management",
"DESCRIPTION": "Manage agent leave requests and approvals",
"HEADER": "Leave Management",
"SUBHEADER": "View and manage leave requests for agents. Approved leaves will exclude agents from automatic assignments.",
"NEW_BUTTON": "Request Leave",
"BACK_BUTTON": "Back to Leaves",
"NEW_HEADER": "Request Leave",
"NEW_DESCRIPTION": "Submit a leave request for approval.",
"DETAILS_HEADER": "Leave Details",
"DETAILS_DESCRIPTION": "View complete information about this leave request.",
"EMPTY": {
"TITLE": "No leave requests found",
"MESSAGE": "There are no leave requests matching your filters."
},
"TABS": {
"ALL": "All Leaves",
"MY_LEAVES": "My Leaves",
"PENDING_APPROVALS": "Pending Approvals"
},
"FILTER": {
"STATUS": "Status"
},
"TABLE": {
"AGENT": "Agent",
"TYPE": "Leave Type",
"DATES": "Dates",
"REASON": "Reason",
"STATUS": "Status",
"ACTIONS": "Actions"
},
"STATUS": {
"PENDING": "Pending",
"APPROVED": "Approved",
"REJECTED": "Rejected"
},
"TYPES": {
"ANNUAL": "Annual Leave",
"SICK": "Sick Leave",
"PERSONAL": "Personal Leave",
"MATERNITY": "Maternity Leave",
"PATERNITY": "Paternity Leave",
"UNPAID": "Unpaid Leave",
"OTHER": "Other"
},
"FORM": {
"TYPE": "Leave Type",
"TYPE_PLACEHOLDER": "Select leave type",
"TYPE_REQUIRED": "Leave type is required",
"START_DATE": "Start Date",
"START_DATE_PLACEHOLDER": "Select start date",
"START_DATE_REQUIRED": "Start date is required",
"END_DATE": "End Date",
"END_DATE_PLACEHOLDER": "Select end date",
"END_DATE_REQUIRED": "End date is required",
"END_DATE_INVALID": "End date must be after start date",
"IS_HALF_DAY": "Half day leave",
"HALF_DAY_PERIOD": "Half Day Period",
"HALF_DAY_INVALID": "Half day leave must be for a single date",
"TOTAL_DAYS": "Total Days",
"REASON": "Reason",
"REASON_PLACEHOLDER": "Provide reason for leave",
"REASON_REQUIRED": "Reason is required",
"SUBMIT": "Submit Request"
},
"HALF_DAY": {
"MORNING": "Morning",
"AFTERNOON": "Afternoon"
},
"DETAILS": {
"TYPE": "Leave Type",
"DATES": "Leave Period",
"REASON": "Reason",
"REQUESTED_ON": "Requested On",
"APPROVED_BY": "Approved By",
"REJECTED_BY": "Rejected By",
"APPROVER_NOTES": "Approver Notes"
},
"APPROVAL_NOTES": "Notes (Optional)",
"APPROVAL_NOTES_PLACEHOLDER": "Add any notes for this decision",
"DELETE": {
"TITLE": "Delete Leave Request",
"MESSAGE": "Are you sure you want to delete this leave request?",
"CONFIRM": "Are you sure you want to delete this leave request?",
"SUCCESS": "Leave request deleted successfully",
"ERROR": "Failed to delete leave request"
},
"CREATE": {
"SUCCESS": "Leave request submitted successfully",
"ERROR": "Failed to submit leave request"
},
"LOAD": {
"ERROR": "Failed to load leave details"
},
"APPROVE": {
"BUTTON": "Approve",
"MODAL_TITLE": "Approve Leave Request",
"MODAL_MESSAGE": "Approve leave request for {name} from {dates}?",
"CONFIRM": "Approve Leave",
"SUCCESS": "Leave request approved successfully",
"ERROR": "Failed to approve leave request"
},
"REJECT": {
"BUTTON": "Reject",
"MODAL_TITLE": "Reject Leave Request",
"MODAL_MESSAGE": "Reject leave request for {name} from {dates}?",
"CONFIRM": "Reject Leave",
"SUCCESS": "Leave request rejected successfully",
"ERROR": "Failed to reject leave request"
}
},
"CAPACITY": {
"TITLE": "Agent Capacity",
"DESCRIPTION": "Set maximum conversation limits for agents (Enterprise)",
"HEADER": "Agent Capacity Management",
"SUBHEADER": "Configure maximum conversation capacity for agents to prevent overload and ensure quality support.",
"NEW_BUTTON": "New Capacity Policy",
"BACK_BUTTON": "Back to Capacity",
"NEW_HEADER": "Create Capacity Policy",
"EDIT_HEADER": "Edit Capacity Policy",
"FORM_DESCRIPTION": "Set the maximum number of concurrent conversations agents can handle.",
"ENTERPRISE_ONLY": "Agent capacity management is available in the Enterprise edition.",
"EMPTY": {
"TITLE": "No capacity policies found",
"MESSAGE": "Create capacity policies to limit the number of concurrent conversations assigned to agents."
},
"AGENTS_EMPTY": {
"TITLE": "No agent capacities configured",
"MESSAGE": "Create capacity policies and assign agents to manage their workload."
},
"TABS": {
"POLICIES": "Capacity Policies",
"AGENTS": "Agent Capacities"
},
"TABLE": {
"NAME": "Policy Name",
"DESCRIPTION": "Description",
"MAX_CAPACITY": "Max Capacity",
"INBOX_LIMITS": "Inbox Limits",
"AGENTS_COUNT": "Agents",
"ACTIONS": "Actions",
"AGENT": "Agent",
"POLICY": "Policy",
"CURRENT_LOAD": "Current Load",
"UTILIZATION": "Utilization"
},
"FORM": {
"NAME": "Policy Name",
"NAME_PLACEHOLDER": "e.g., Standard Agent Capacity",
"NAME_REQUIRED": "Policy name is required",
"DESCRIPTION": "Description",
"DESCRIPTION_PLACEHOLDER": "Describe this capacity policy",
"MAX_CAPACITY": "Maximum Capacity",
"MAX_CAPACITY_PLACEHOLDER": "10",
"MAX_CAPACITY_HELP": "Maximum number of concurrent conversations per agent",
"CAPACITY_REQUIRED": "Capacity must be at least 1",
"AGENTS": "Assign Agents",
"AGENTS_PLACEHOLDER": "Select agents for this policy",
"AGENTS_REQUIRED": "Please select at least one agent",
"NOTE_TITLE": "Note",
"NOTE_MESSAGE": "Agents can only be assigned to one capacity policy at a time. Assigning an agent to this policy will remove them from any other capacity policy."
},
"DELETE": {
"TITLE": "Delete Capacity Policy",
"MESSAGE": "Are you sure you want to delete the policy '{name}'? This will remove capacity limits for all assigned agents.",
"SUCCESS": "Capacity policy deleted successfully",
"ERROR": "Failed to delete capacity policy"
},
"CREATE": {
"SUCCESS": "Capacity policy created successfully",
"ERROR": "Failed to create capacity policy"
},
"UPDATE": {
"SUCCESS": "Capacity policy updated successfully",
"ERROR": "Failed to update capacity policy"
},
"LOAD": {
"ERROR": "Failed to load capacity policy"
},
"AVAILABLE_ON": "Agent capacity management is available on <b>Enterprise</b> plans.",
"UPGRADE_PROMPT": "Upgrade to Enterprise to set conversation limits for your agents."
},
"METRICS": {
"TITLE": "Assignment Metrics",
"DESCRIPTION": "View detailed analytics and performance metrics",
"HEADER": "Assignment Metrics",
"SUBHEADER": "Monitor assignment performance, agent utilization, and policy effectiveness.",
"EXPORT": "Export Report",
"FILTERS": {
"TITLE": "Filters",
"AGENTS": "Agents",
"POLICIES": "Policies",
"POLICIES_PLACEHOLDER": "All policies"
},
"TABS": {
"OVERVIEW": "Overview",
"AGENT_PERFORMANCE": "Agent Performance",
"POLICY_PERFORMANCE": "Policy Performance",
"UTILIZATION": "Utilization"
},
"CARDS": {
"TOTAL_ASSIGNMENTS": "Total Assignments",
"AVG_RESPONSE_TIME": "Avg Response Time",
"SATISFACTION_SCORE": "Satisfaction Score",
"REASSIGNMENT_RATE": "Reassignment Rate"
},
"CHARTS": {
"ASSIGNMENT_TRENDS": "Assignment Trends",
"ASSIGNMENTS": "Assignments",
"UTILIZATION": "Utilization %",
"POLICY_DISTRIBUTION": "Assignments by Policy",
"POLICY_SUCCESS_RATES": "Policy Success Rates",
"SUCCESS_RATE": "Success Rate %",
"AGENT_UTILIZATION": "Agent Utilization"
},
"TABLE": {
"AGENT": "Agent",
"ASSIGNMENTS_HANDLED": "Assignments",
"AVG_RESPONSE_TIME": "Avg Response",
"SATISFACTION_SCORE": "Satisfaction",
"EFFICIENCY_SCORE": "Efficiency",
"POLICY": "Policy",
"TOTAL_ASSIGNMENTS": "Total",
"SUCCESS_RATE": "Success Rate",
"AVG_HANDLING_TIME": "Avg Handling",
"REASSIGNMENT_RATE": "Reassignments"
}
}
}
}
@@ -246,6 +246,18 @@
},
"COMMON": {
"OR": "Or",
"CLICK_HERE": "click here"
"CLICK_HERE": "click here",
"DELETE": "Delete",
"CANCEL": "Cancel",
"EDIT": "Edit",
"UPDATE": "Update",
"CREATE": "Create",
"SAVE": "Save",
"AGENTS": "Agents",
"VIEW": "View",
"ACTIVE": "Active",
"INACTIVE": "Inactive",
"ALL": "All",
"DAYS": "Days"
}
}
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -1,6 +1,7 @@
import advancedFilters from './advancedFilters.json';
import agentBots from './agentBots.json';
import agentMgmt from './agentMgmt.json';
import assignmentSettings from './assignmentSettings.json';
import attributesMgmt from './attributesMgmt.json';
import auditLogs from './auditLogs.json';
import automation from './automation.json';
@@ -40,6 +41,7 @@ export default {
...advancedFilters,
...agentBots,
...agentMgmt,
...assignmentSettings,
...attributesMgmt,
...auditLogs,
...automation,
@@ -308,6 +308,7 @@
"MACROS": "Macros",
"TEAMS": "Teams",
"BILLING": "Billing",
"ASSIGNMENT": "Assignment",
"CUSTOM_VIEWS_FOLDER": "Folders",
"CUSTOM_VIEWS_SEGMENTS": "Segments",
"ALL_CONTACTS": "All Contacts",
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Actualizar",
"WHATSAPP_WEBHOOK_TITLE": "Token de verificación del Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "Este token se utiliza para verificar la autenticidad del extremo del webhook.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Actualizar configuración de Formulario de Chat"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Buscar plantillas",
"NO_TEMPLATES_FOUND": "No se encontraron plantillas para",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Idioma",
"TEMPLATE_BODY": "Cuerpo de plantilla",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "اعمال شود",
"WHATSAPP_WEBHOOK_TITLE": "توکن تایید Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "این توکن برای تأیید صحت نقطه پایانی webhook استفاده می شود.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "به‌روزرسانی تنظیمات فرم قبل از گفتگو"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "جستجوی الگوها",
"NO_TEMPLATES_FOUND": "هیچ قالبی برای",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "زبان",
"TEMPLATE_BODY": "بدنه الگو",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Päivitä",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Etsi Pohjia",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Mettre à jour",
"WHATSAPP_WEBHOOK_TITLE": "Jeton de vérification du Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "Ce jeton est utilisé pour vérifier l'authenticité du point de terminaison du webhook.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Mettre à jour les paramètres du formulaire de pré-chat"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Rechercher des modèles",
"NO_TEMPLATES_FOUND": "Aucun modèle trouvé pour",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Langue",
"TEMPLATE_BODY": "Corps du modèle",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "עדכן",
"WHATSAPP_WEBHOOK_TITLE": "אסימון אימות Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "עדכון הגדרות טופס טרום צ'אט"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "חפש תבניות",
"NO_TEMPLATES_FOUND": "לא נמצאו תבניות עבור",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "שפה",
"TEMPLATE_BODY": "גוף התבנית",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "Ovaj token se koristi za verifikaciju autentičnosti webhook endpoint-a.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Pretraži Predloške",
"NO_TEMPLATES_FOUND": "Nije pronađen predložak za",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Jezik",
"TEMPLATE_BODY": "Tijelo predloška",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Frissítés",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "Ez a token a webhook-végpont hitelességének ellenőrzésére szolgál.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Csevegés előtti űrlap beállításainak frissítése"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Sablon keresése",
"NO_TEMPLATES_FOUND": "Nem található sablon erre:",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Nyelv",
"TEMPLATE_BODY": "Sablon törzse",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Perbarui",
"WHATSAPP_WEBHOOK_TITLE": "Token Verifikasi Webhook",
"WHATSAPP_WEBHOOK_SUBHEADER": "Token ini digunakan untuk memverifikasi keaslian titik akhir webhook.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Perbarui Pengaturan Formulir Pra Obrolan"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Cari Templat",
"NO_TEMPLATES_FOUND": "Tidak ditemukan templat untuk",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Bahasa",
"TEMPLATE_BODY": "Isi Templat",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Uppfæra",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Aggiorna",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Cerca modelli",
"NO_TEMPLATES_FOUND": "Nessun modello trovato per",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Lingua",
"TEMPLATE_BODY": "Corpo modello",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "更新",
"WHATSAPP_WEBHOOK_TITLE": "Webhook検証トークン",
"WHATSAPP_WEBHOOK_SUBHEADER": "このトークンはWebhookエンドポイントの信頼性を検証するために使用されます。",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "プレチャットフォーム設定を更新する"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "テンプレートを検索",
"NO_TEMPLATES_FOUND": "該当するテンプレートが見つかりません:",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "言語",
"TEMPLATE_BODY": "テンプレート本文",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "업데이트",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "언어",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Atnaujinti",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Patikros Prieigos Raktas",
"WHATSAPP_WEBHOOK_SUBHEADER": "Šis prieigos raktas naudojamas „webhook“ galutinio taško autentiškumui patikrinti.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Atnaujinkite išankstinio pokalbio internetu formos nustatymus"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Ieškoti šablonų",
"NO_TEMPLATES_FOUND": "Šablonų nerasta",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Kalba",
"TEMPLATE_BODY": "Šablono tekstas",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Atjaunināt",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verifikācijas Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "Šis marķieris tiek izmantots, lai pārbaudītu webhook endpoint autentiskumu.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Atjaunināt pirms-tērzēšanas veidlapas iestatījumus"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Meklēt Veidnes",
"NO_TEMPLATES_FOUND": "Veidnes nav atrastas",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Valoda",
"TEMPLATE_BODY": "Veidnes Pamatteksts",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "അപ്‌ഡേറ്റ്",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Vernieuwen",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Templates zoeken",
"NO_TEMPLATES_FOUND": "Geen templates gevonden voor",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Taal",
"TEMPLATE_BODY": "Template bericht",
@@ -600,6 +600,10 @@
"WHATSAPP_SECTION_UPDATE_BUTTON": "Oppdater",
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
"WHATSAPP_TEMPLATES_SYNC_TITLE": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUBHEADER": "Manually sync message templates from WhatsApp to update your available templates.",
"WHATSAPP_TEMPLATES_SYNC_BUTTON": "Sync Templates",
"WHATSAPP_TEMPLATES_SYNC_SUCCESS": "Templates sync initiated successfully. It may take a couple of minutes to update.",
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
},
"HELP_CENTER": {
@@ -8,6 +8,10 @@
"PICKER": {
"SEARCH_PLACEHOLDER": "Search Templates",
"NO_TEMPLATES_FOUND": "No templates found for",
"NO_TEMPLATES_AVAILABLE": "No WhatsApp templates available. Click refresh to sync templates from WhatsApp.",
"REFRESH_BUTTON": "Refresh templates",
"REFRESH_SUCCESS": "Templates refresh initiated. It may take a couple of minutes to update.",
"REFRESH_ERROR": "Failed to refresh templates. Please try again.",
"LABELS": {
"LANGUAGE": "Language",
"TEMPLATE_BODY": "Template Body",

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