Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c0cf718b2 |
@@ -91,6 +91,3 @@ yarn-debug.log*
|
|||||||
# Vite uses dotenv and suggests to ignore local-only env files. See
|
# Vite uses dotenv and suggests to ignore local-only env files. See
|
||||||
# https://vitejs.dev/guide/env-and-mode.html#env-files
|
# https://vitejs.dev/guide/env-and-mode.html#env-files
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Claude.ai config file
|
|
||||||
CLAUDE.md
|
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ gem 'pgvector'
|
|||||||
gem 'reverse_markdown'
|
gem 'reverse_markdown'
|
||||||
|
|
||||||
gem 'iso-639'
|
gem 'iso-639'
|
||||||
gem 'ruby_llm', '1.1.0rc1'
|
|
||||||
gem 'ruby-openai'
|
gem 'ruby-openai'
|
||||||
|
|
||||||
gem 'shopify_api'
|
gem 'shopify_api'
|
||||||
|
|||||||
@@ -698,13 +698,6 @@ GEM
|
|||||||
ruby2ruby (2.5.0)
|
ruby2ruby (2.5.0)
|
||||||
ruby_parser (~> 3.1)
|
ruby_parser (~> 3.1)
|
||||||
sexp_processor (~> 4.6)
|
sexp_processor (~> 4.6)
|
||||||
ruby_llm (1.1.0rc1)
|
|
||||||
base64
|
|
||||||
event_stream_parser (~> 1)
|
|
||||||
faraday (~> 2)
|
|
||||||
faraday-multipart (~> 1)
|
|
||||||
faraday-retry (~> 2)
|
|
||||||
zeitwerk (~> 2)
|
|
||||||
ruby_parser (3.20.0)
|
ruby_parser (3.20.0)
|
||||||
sexp_processor (~> 4.16)
|
sexp_processor (~> 4.16)
|
||||||
sass (3.7.4)
|
sass (3.7.4)
|
||||||
@@ -971,7 +964,6 @@ DEPENDENCIES
|
|||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
ruby-openai
|
ruby-openai
|
||||||
ruby_llm (= 1.1.0rc1)
|
|
||||||
scout_apm
|
scout_apm
|
||||||
scss_lint
|
scss_lint
|
||||||
seed_dump
|
seed_dump
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::Contacts::BaseController
|
class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::Contacts::BaseController
|
||||||
def index
|
def index
|
||||||
# Start with all conversations for this contact
|
@conversations = Current.account.conversations.includes(
|
||||||
conversations = Current.account.conversations.includes(
|
|
||||||
:assignee, :contact, :inbox, :taggings
|
:assignee, :contact, :inbox, :taggings
|
||||||
).where(contact_id: @contact.id)
|
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(last_activity_at: :desc).limit(20)
|
||||||
|
end
|
||||||
|
|
||||||
# Apply permission-based filtering using the existing service
|
private
|
||||||
conversations = Conversations::PermissionFilterService.new(
|
|
||||||
conversations,
|
|
||||||
Current.user,
|
|
||||||
Current.account
|
|
||||||
).perform
|
|
||||||
|
|
||||||
# Only allow conversations from inboxes the user has access to
|
def inbox_ids
|
||||||
inbox_ids = Current.user.assigned_inboxes.pluck(:id)
|
if Current.user.administrator? || Current.user.agent?
|
||||||
conversations = conversations.where(inbox_id: inbox_ids)
|
Current.user.assigned_inboxes.pluck(:id)
|
||||||
|
else
|
||||||
@conversations = conversations.order(last_activity_at: :desc).limit(20)
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filter
|
def filter
|
||||||
result = ::Conversations::FilterService.new(params.permit!, current_user, current_account).perform
|
result = ::Conversations::FilterService.new(params.permit!, current_user).perform
|
||||||
@conversations = result[:conversations]
|
@conversations = result[:conversations]
|
||||||
@conversations_count = result[:count]
|
@conversations_count = result[:count]
|
||||||
rescue CustomExceptions::CustomFilter::InvalidAttribute,
|
rescue CustomExceptions::CustomFilter::InvalidAttribute,
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::BaseController
|
|
||||||
include InstagramConcern
|
|
||||||
include Instagram::IntegrationHelper
|
|
||||||
before_action :check_authorization
|
|
||||||
|
|
||||||
def create
|
|
||||||
# https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#step-1--get-authorization
|
|
||||||
redirect_url = instagram_client.auth_code.authorize_url(
|
|
||||||
{
|
|
||||||
redirect_uri: "#{base_url}/instagram/callback",
|
|
||||||
scope: REQUIRED_SCOPES.join(','),
|
|
||||||
enable_fb_login: '0',
|
|
||||||
force_authentication: '1',
|
|
||||||
response_type: 'code',
|
|
||||||
state: generate_instagram_token(Current.account.id)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if redirect_url
|
|
||||||
render json: { success: true, url: redirect_url }
|
|
||||||
else
|
|
||||||
render json: { success: false }, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def check_authorization
|
|
||||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -9,6 +9,11 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
|||||||
@portals = Current.account.portals
|
@portals = Current.account.portals
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_members
|
||||||
|
agents = Current.account.agents.where(id: portal_member_params[:member_ids])
|
||||||
|
@portal.members << agents
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@all_articles = @portal.articles
|
@all_articles = @portal.articles
|
||||||
@articles = @all_articles.search(locale: params[:locale])
|
@articles = @all_articles.search(locale: params[:locale])
|
||||||
@@ -80,6 +85,10 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
|||||||
{ channel_web_widget_id: inbox.channel.id }
|
{ channel_web_widget_id: inbox.channel.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def portal_member_params
|
||||||
|
params.require(:portal).permit(:account_id, member_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
def set_current_page
|
def set_current_page
|
||||||
@current_page = params[:page] || 1
|
@current_page = params[:page] || 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def check_authorization
|
def check_authorization
|
||||||
authorize :report, :view?
|
return if Current.account_user.administrator?
|
||||||
|
|
||||||
|
raise Pundit::NotAuthorizedError
|
||||||
end
|
end
|
||||||
|
|
||||||
def common_params
|
def common_params
|
||||||
@@ -135,3 +137,5 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
V2::ReportBuilder.new(Current.account, conversation_params).conversation_metrics
|
V2::ReportBuilder.new(Current.account, conversation_params).conversation_metrics
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Api::V2::Accounts::ReportsController.prepend_mod_with('Api::V2::Accounts::ReportsController')
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
module InstagramConcern
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
include HTTParty
|
|
||||||
|
|
||||||
def instagram_client
|
|
||||||
::OAuth2::Client.new(
|
|
||||||
client_id,
|
|
||||||
client_secret,
|
|
||||||
{
|
|
||||||
site: 'https://api.instagram.com',
|
|
||||||
authorize_url: 'https://api.instagram.com/oauth/authorize',
|
|
||||||
token_url: 'https://api.instagram.com/oauth/access_token',
|
|
||||||
auth_scheme: :request_body,
|
|
||||||
token_method: :post
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def client_id
|
|
||||||
GlobalConfigService.load('INSTAGRAM_APP_ID', nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def client_secret
|
|
||||||
GlobalConfigService.load('INSTAGRAM_APP_SECRET', nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def exchange_for_long_lived_token(short_lived_token)
|
|
||||||
endpoint = 'https://graph.instagram.com/access_token'
|
|
||||||
params = {
|
|
||||||
grant_type: 'ig_exchange_token',
|
|
||||||
client_secret: client_secret,
|
|
||||||
access_token: short_lived_token,
|
|
||||||
client_id: client_id
|
|
||||||
}
|
|
||||||
|
|
||||||
make_api_request(endpoint, params, 'Failed to exchange token')
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_instagram_user_details(access_token)
|
|
||||||
endpoint = 'https://graph.instagram.com/v22.0/me'
|
|
||||||
params = {
|
|
||||||
fields: 'id,username,user_id,name,profile_picture_url,account_type',
|
|
||||||
access_token: access_token
|
|
||||||
}
|
|
||||||
|
|
||||||
make_api_request(endpoint, params, 'Failed to fetch Instagram user details')
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_api_request(endpoint, params, error_prefix)
|
|
||||||
response = HTTParty.get(
|
|
||||||
endpoint,
|
|
||||||
query: params,
|
|
||||||
headers: { 'Accept' => 'application/json' }
|
|
||||||
)
|
|
||||||
|
|
||||||
unless response.success?
|
|
||||||
Rails.logger.error "#{error_prefix}. Status: #{response.code}, Body: #{response.body}"
|
|
||||||
raise "#{error_prefix}: #{response.body}"
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
JSON.parse(response.body)
|
|
||||||
rescue JSON::ParserError => e
|
|
||||||
ChatwootExceptionTracker.new(e).capture_exception
|
|
||||||
Rails.logger.error "Invalid JSON response: #{response.body}"
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def base_url
|
|
||||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
class Instagram::CallbacksController < ApplicationController
|
|
||||||
include InstagramConcern
|
|
||||||
include Instagram::IntegrationHelper
|
|
||||||
|
|
||||||
def show
|
|
||||||
# Check if Instagram redirected with an error (user canceled authorization)
|
|
||||||
# See: https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#canceled-authorization
|
|
||||||
if params[:error].present?
|
|
||||||
handle_authorization_error
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
process_successful_authorization
|
|
||||||
rescue StandardError => e
|
|
||||||
handle_error(e)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Process the authorization code and create inbox
|
|
||||||
def process_successful_authorization
|
|
||||||
@response = instagram_client.auth_code.get_token(
|
|
||||||
oauth_code,
|
|
||||||
redirect_uri: "#{base_url}/#{provider_name}/callback",
|
|
||||||
grant_type: 'authorization_code'
|
|
||||||
)
|
|
||||||
|
|
||||||
@long_lived_token_response = exchange_for_long_lived_token(@response.token)
|
|
||||||
inbox, already_exists = find_or_create_inbox
|
|
||||||
|
|
||||||
if already_exists
|
|
||||||
redirect_to app_instagram_inbox_settings_url(account_id: account_id, inbox_id: inbox.id)
|
|
||||||
else
|
|
||||||
redirect_to app_instagram_inbox_agents_url(account_id: account_id, inbox_id: inbox.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handle all errors that might occur during authorization
|
|
||||||
# https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#sample-rejected-response
|
|
||||||
def handle_error(error)
|
|
||||||
Rails.logger.error("Instagram Channel creation Error: #{error.message}")
|
|
||||||
ChatwootExceptionTracker.new(error).capture_exception
|
|
||||||
|
|
||||||
error_info = extract_error_info(error)
|
|
||||||
redirect_to_error_page(error_info)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extract error details from the exception
|
|
||||||
def extract_error_info(error)
|
|
||||||
if error.is_a?(OAuth2::Error)
|
|
||||||
begin
|
|
||||||
# Instagram returns JSON error response which we parse to extract error details
|
|
||||||
JSON.parse(error.message)
|
|
||||||
rescue JSON::ParseError
|
|
||||||
# Fall back to a generic OAuth error if JSON parsing fails
|
|
||||||
{ 'error_type' => 'OAuthException', 'code' => 400, 'error_message' => error.message }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# For other unexpected errors
|
|
||||||
{ 'error_type' => error.class.name, 'code' => 500, 'error_message' => error.message }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handles the case when a user denies permissions or cancels the authorization flow
|
|
||||||
# Error parameters are documented at:
|
|
||||||
# https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#canceled-authorization
|
|
||||||
def handle_authorization_error
|
|
||||||
error_info = {
|
|
||||||
'error_type' => params[:error] || 'authorization_error',
|
|
||||||
'code' => 400,
|
|
||||||
'error_message' => params[:error_description] || 'Authorization was denied'
|
|
||||||
}
|
|
||||||
|
|
||||||
Rails.logger.error("Instagram Authorization Error: #{error_info['error_message']}")
|
|
||||||
redirect_to_error_page(error_info)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Centralized method to redirect to error page with appropriate parameters
|
|
||||||
# This ensures consistent error handling across different error scenarios
|
|
||||||
# Frontend will handle the error page based on the error_type
|
|
||||||
def redirect_to_error_page(error_info)
|
|
||||||
redirect_to app_new_instagram_inbox_url(
|
|
||||||
account_id: account_id,
|
|
||||||
error_type: error_info['error_type'],
|
|
||||||
code: error_info['code'],
|
|
||||||
error_message: error_info['error_message']
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_or_create_inbox
|
|
||||||
user_details = fetch_instagram_user_details(@long_lived_token_response['access_token'])
|
|
||||||
channel_instagram = find_channel_by_instagram_id(user_details['user_id'].to_s)
|
|
||||||
channel_exists = channel_instagram.present?
|
|
||||||
|
|
||||||
if channel_instagram
|
|
||||||
update_channel(channel_instagram, user_details)
|
|
||||||
else
|
|
||||||
channel_instagram = create_channel_with_inbox(user_details)
|
|
||||||
end
|
|
||||||
|
|
||||||
# reauthorize channel, this code path only triggers when instagram auth is successful
|
|
||||||
# reauthorized will also update cache keys for the associated inbox
|
|
||||||
channel_instagram.reauthorized!
|
|
||||||
|
|
||||||
[channel_instagram.inbox, channel_exists]
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_channel_by_instagram_id(instagram_id)
|
|
||||||
Channel::Instagram.find_by(instagram_id: instagram_id, account: account)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_channel(channel_instagram, user_details)
|
|
||||||
expires_at = Time.current + @long_lived_token_response['expires_in'].seconds
|
|
||||||
|
|
||||||
channel_instagram.update!(
|
|
||||||
access_token: @long_lived_token_response['access_token'],
|
|
||||||
expires_at: expires_at
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update inbox name if username changed
|
|
||||||
channel_instagram.inbox.update!(name: user_details['username'])
|
|
||||||
channel_instagram
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_channel_with_inbox(user_details)
|
|
||||||
ActiveRecord::Base.transaction do
|
|
||||||
expires_at = Time.current + @long_lived_token_response['expires_in'].seconds
|
|
||||||
|
|
||||||
channel_instagram = Channel::Instagram.create!(
|
|
||||||
access_token: @long_lived_token_response['access_token'],
|
|
||||||
instagram_id: user_details['user_id'].to_s,
|
|
||||||
account: account,
|
|
||||||
expires_at: expires_at
|
|
||||||
)
|
|
||||||
|
|
||||||
account.inboxes.create!(
|
|
||||||
account: account,
|
|
||||||
channel: channel_instagram,
|
|
||||||
name: user_details['username']
|
|
||||||
)
|
|
||||||
|
|
||||||
channel_instagram
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_id
|
|
||||||
return unless params[:state]
|
|
||||||
|
|
||||||
verify_instagram_token(params[:state])
|
|
||||||
end
|
|
||||||
|
|
||||||
def oauth_code
|
|
||||||
params[:code]
|
|
||||||
end
|
|
||||||
|
|
||||||
def account
|
|
||||||
@account ||= Account.find(account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def provider_name
|
|
||||||
'instagram'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -43,8 +43,6 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
|
|||||||
['MAILER_INBOUND_EMAIL_DOMAIN']
|
['MAILER_INBOUND_EMAIL_DOMAIN']
|
||||||
when 'linear'
|
when 'linear'
|
||||||
%w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET]
|
%w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET]
|
||||||
when 'instagram'
|
|
||||||
%w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT]
|
|
||||||
else
|
else
|
||||||
%w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]
|
%w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ class Webhooks::InstagramController < ActionController::API
|
|||||||
private
|
private
|
||||||
|
|
||||||
def valid_token?(token)
|
def valid_token?(token)
|
||||||
# Validates against both IG_VERIFY_TOKEN (Instagram channel via Facebook page) and
|
token == GlobalConfigService.load('IG_VERIFY_TOKEN', '')
|
||||||
# INSTAGRAM_VERIFY_TOKEN (Instagram channel via direct Instagram login)
|
|
||||||
token == GlobalConfigService.load('IG_VERIFY_TOKEN', '') ||
|
|
||||||
token == GlobalConfigService.load('INSTAGRAM_VERIFY_TOKEN', '')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -81,8 +81,7 @@ class AccountDashboard < Administrate::BaseDashboard
|
|||||||
COLLECTION_FILTERS = {
|
COLLECTION_FILTERS = {
|
||||||
active: ->(resources) { resources.where(status: :active) },
|
active: ->(resources) { resources.where(status: :active) },
|
||||||
suspended: ->(resources) { resources.where(status: :suspended) },
|
suspended: ->(resources) { resources.where(status: :suspended) },
|
||||||
recent: ->(resources) { resources.where('created_at > ?', 30.days.ago) },
|
recent: ->(resources) { resources.where('created_at > ?', 30.days.ago) }
|
||||||
marked_for_deletion: ->(resources) { resources.where("custom_attributes->>'marked_for_deletion_at' IS NOT NULL") }
|
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# Overwrite this method to customize how accounts are displayed
|
# Overwrite this method to customize how accounts are displayed
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class ConversationFinder
|
|||||||
def initialize(current_user, params)
|
def initialize(current_user, params)
|
||||||
@current_user = current_user
|
@current_user = current_user
|
||||||
@current_account = current_user.account
|
@current_account = current_user.account
|
||||||
@is_admin = current_account.account_users.find_by(user_id: current_user.id)&.administrator?
|
|
||||||
@params = params
|
@params = params
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -86,19 +85,8 @@ class ConversationFinder
|
|||||||
@team = current_account.teams.find(params[:team_id]) if params[:team_id]
|
@team = current_account.teams.find(params[:team_id]) if params[:team_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_conversation_by_inbox
|
|
||||||
@conversations = current_account.conversations
|
|
||||||
@conversations = @conversations.where(inbox_id: @inbox_ids) unless params[:inbox_id].blank? && @is_admin
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_all_conversations
|
def find_all_conversations
|
||||||
find_conversation_by_inbox
|
@conversations = current_account.conversations.where(inbox_id: @inbox_ids)
|
||||||
# Apply permission-based filtering
|
|
||||||
@conversations = Conversations::PermissionFilterService.new(
|
|
||||||
@conversations,
|
|
||||||
current_user,
|
|
||||||
current_account
|
|
||||||
).perform
|
|
||||||
filter_by_conversation_type if params[:conversation_type]
|
filter_by_conversation_type if params[:conversation_type]
|
||||||
@conversations
|
@conversations
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,8 +18,4 @@ module BillingHelper
|
|||||||
def non_web_inboxes(account)
|
def non_web_inboxes(account)
|
||||||
account.inboxes.where.not(channel_type: Channel::WebWidget.to_s).count
|
account.inboxes.where.not(channel_type: Channel::WebWidget.to_s).count
|
||||||
end
|
end
|
||||||
|
|
||||||
def agents(account)
|
|
||||||
account.users.count
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
module Instagram::IntegrationHelper
|
|
||||||
REQUIRED_SCOPES = %w[instagram_business_basic instagram_business_manage_messages].freeze
|
|
||||||
|
|
||||||
# Generates a signed JWT token for Instagram integration
|
|
||||||
#
|
|
||||||
# @param account_id [Integer] The account ID to encode in the token
|
|
||||||
# @return [String, nil] The encoded JWT token or nil if client secret is missing
|
|
||||||
def generate_instagram_token(account_id)
|
|
||||||
return if client_secret.blank?
|
|
||||||
|
|
||||||
JWT.encode(token_payload(account_id), client_secret, 'HS256')
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error("Failed to generate Instagram token: #{e.message}")
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def token_payload(account_id)
|
|
||||||
{
|
|
||||||
sub: account_id,
|
|
||||||
iat: Time.current.to_i
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Verifies and decodes a Instagram JWT token
|
|
||||||
#
|
|
||||||
# @param token [String] The JWT token to verify
|
|
||||||
# @return [Integer, nil] The account ID from the token or nil if invalid
|
|
||||||
def verify_instagram_token(token)
|
|
||||||
return if token.blank? || client_secret.blank?
|
|
||||||
|
|
||||||
decode_token(token, client_secret)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def client_secret
|
|
||||||
@client_secret ||= GlobalConfigService.load('INSTAGRAM_APP_SECRET', nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def decode_token(token, secret)
|
|
||||||
JWT.decode(token, secret, true, {
|
|
||||||
algorithm: 'HS256',
|
|
||||||
verify_expiration: true
|
|
||||||
}).first['sub']
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error("Unexpected error verifying Instagram token: #{e.message}")
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -4,6 +4,7 @@ import AddAccountModal from '../dashboard/components/layout/sidebarComponents/Ad
|
|||||||
import LoadingState from './components/widgets/LoadingState.vue';
|
import LoadingState from './components/widgets/LoadingState.vue';
|
||||||
import NetworkNotification from './components/NetworkNotification.vue';
|
import NetworkNotification from './components/NetworkNotification.vue';
|
||||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||||
|
import UpgradeBanner from './components/app/UpgradeBanner.vue';
|
||||||
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
||||||
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
|
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
|
||||||
import vueActionCable from './helper/actionCable';
|
import vueActionCable from './helper/actionCable';
|
||||||
@@ -30,6 +31,7 @@ export default {
|
|||||||
UpdateBanner,
|
UpdateBanner,
|
||||||
PaymentPendingBanner,
|
PaymentPendingBanner,
|
||||||
WootSnackbarBox,
|
WootSnackbarBox,
|
||||||
|
UpgradeBanner,
|
||||||
PendingEmailVerificationBanner,
|
PendingEmailVerificationBanner,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
@@ -144,6 +146,7 @@ export default {
|
|||||||
<template v-if="currentAccountId">
|
<template v-if="currentAccountId">
|
||||||
<PendingEmailVerificationBanner v-if="hideOnOnboardingView" />
|
<PendingEmailVerificationBanner v-if="hideOnOnboardingView" />
|
||||||
<PaymentPendingBanner v-if="hideOnOnboardingView" />
|
<PaymentPendingBanner v-if="hideOnOnboardingView" />
|
||||||
|
<UpgradeBanner />
|
||||||
</template>
|
</template>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
/* global axios */
|
|
||||||
import ApiClient from '../ApiClient';
|
|
||||||
|
|
||||||
class InstagramChannel extends ApiClient {
|
|
||||||
constructor() {
|
|
||||||
super('instagram', { accountScoped: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
generateAuthorization(payload) {
|
|
||||||
return axios.post(`${this.url}/authorization`, payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new InstagramChannel();
|
|
||||||
@@ -17,12 +17,6 @@ class EnterpriseAccountAPI extends ApiClient {
|
|||||||
getLimits() {
|
getLimits() {
|
||||||
return axios.get(`${this.url}limits`);
|
return axios.get(`${this.url}limits`);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDeletion(action) {
|
|
||||||
return axios.post(`${this.url}toggle_deletion`, {
|
|
||||||
action_type: action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new EnterpriseAccountAPI();
|
export default new EnterpriseAccountAPI();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ describe('#enterpriseAccountAPI', () => {
|
|||||||
expect(accountAPI).toHaveProperty('update');
|
expect(accountAPI).toHaveProperty('update');
|
||||||
expect(accountAPI).toHaveProperty('delete');
|
expect(accountAPI).toHaveProperty('delete');
|
||||||
expect(accountAPI).toHaveProperty('checkout');
|
expect(accountAPI).toHaveProperty('checkout');
|
||||||
expect(accountAPI).toHaveProperty('toggleDeletion');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('API calls', () => {
|
describe('API calls', () => {
|
||||||
@@ -43,21 +42,5 @@ describe('#enterpriseAccountAPI', () => {
|
|||||||
'/enterprise/api/v1/subscription'
|
'/enterprise/api/v1/subscription'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#toggleDeletion with delete action', () => {
|
|
||||||
accountAPI.toggleDeletion('delete');
|
|
||||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
|
||||||
'/enterprise/api/v1/toggle_deletion',
|
|
||||||
{ action_type: 'delete' }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('#toggleDeletion with undelete action', () => {
|
|
||||||
accountAPI.toggleDeletion('undelete');
|
|
||||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
|
||||||
'/enterprise/api/v1/toggle_deletion',
|
|
||||||
{ action_type: 'undelete' }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,6 +81,28 @@
|
|||||||
margin-left: var(--space-small);
|
margin-left: var(--space-small);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conversation sidebar close button
|
||||||
|
.close-button--rtl {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve actions button
|
||||||
|
.resolve-actions {
|
||||||
|
.button-group .button:first-child {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: var(--border-radius-normal);
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: var(--border-radius-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group .button:last-child {
|
||||||
|
border-bottom-left-radius: var(--border-radius-normal);
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-top-left-radius: var(--border-radius-normal);
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversation list
|
// Conversation list
|
||||||
@@ -155,6 +177,71 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Help center
|
||||||
|
.article-container .row--article-block {
|
||||||
|
td:last-child {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.portal-popover__container .portal {
|
||||||
|
.actions-container {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: var(--space-one);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-article--container {
|
||||||
|
.header-right--wrap {
|
||||||
|
.button-group .button:first-child {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: var(--border-radius-normal);
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: var(--border-radius-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group .button:last-child {
|
||||||
|
border-bottom-left-radius: var(--border-radius-normal);
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-top-left-radius: var(--border-radius-normal);
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left--wrap {
|
||||||
|
.back-button {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article--buttons {
|
||||||
|
.dropdown-pane {
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-settings--container {
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 1px solid var(--color-border-light);
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: var(--space-normal);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: var(--space-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list--container .header-left--wrap {
|
||||||
|
direction: initial;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle switch
|
// Toggle switch
|
||||||
.toggle-button {
|
.toggle-button {
|
||||||
&.small {
|
&.small {
|
||||||
@@ -177,6 +264,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Widget builder
|
||||||
|
.widget-builder-container .widget-preview {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
// Modal
|
// Modal
|
||||||
.modal-container {
|
.modal-container {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -190,6 +282,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Other changes
|
// Other changes
|
||||||
|
|
||||||
.colorpicker--chrome {
|
.colorpicker--chrome {
|
||||||
direction: initial;
|
direction: initial;
|
||||||
}
|
}
|
||||||
@@ -198,6 +291,14 @@
|
|||||||
direction: initial;
|
direction: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact--details .contact--bio {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-contacts .child-contact-wrap {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
.contact--form .input-group {
|
.contact--form .input-group {
|
||||||
direction: initial;
|
direction: initial;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
@import 'rtl';
|
@import 'rtl';
|
||||||
|
|
||||||
@import 'widgets/base';
|
@import 'widgets/base';
|
||||||
|
@import 'widgets/buttons';
|
||||||
@import 'widgets/conversation-view';
|
@import 'widgets/conversation-view';
|
||||||
@import 'widgets/tabs';
|
@import 'widgets/tabs';
|
||||||
@import 'widgets/woot-tables';
|
@import 'widgets/woot-tables';
|
||||||
|
|||||||
@@ -40,12 +40,6 @@ dl:not(.reset-base) {
|
|||||||
@apply mb-0;
|
@apply mb-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button base
|
|
||||||
button {
|
|
||||||
font-family: inherit;
|
|
||||||
@apply inline-block text-center align-middle cursor-pointer text-sm m-0 py-1 px-2.5 transition-all duration-200 ease-in-out border-0 border-none rounded-lg disabled:opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Form elements
|
// Form elements
|
||||||
// -------------------------
|
// -------------------------
|
||||||
label {
|
label {
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
// scss-lint:disable SpaceAfterPropertyColon
|
||||||
|
// scss-lint:disable MergeableSelector
|
||||||
|
button {
|
||||||
|
font-family: inherit;
|
||||||
|
transition:
|
||||||
|
background-color 0.25s ease-out,
|
||||||
|
color 0.25s ease-out;
|
||||||
|
@apply inline-block items-center mb-0 text-center align-middle cursor-pointer text-sm mt-0 mx-0 py-1 px-2.5 border border-solid border-transparent dark:border-transparent rounded-[0.3125rem];
|
||||||
|
|
||||||
|
&:disabled,
|
||||||
|
&.disabled {
|
||||||
|
@apply opacity-40 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
@apply mb-0 flex flex-nowrap items-stretch;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
@apply m-0 text-sm rounded-none first:rounded-tl-[0.3125rem] first:rounded-bl-[0.3125rem] last:rounded-tr-[0.3125rem] last:rounded-br-[0.3125rem] rtl:space-x-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--only-icon {
|
||||||
|
@apply w-10 justify-center pl-0 pr-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
@apply m-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
@apply items-center bg-n-brand px-2.5 text-white dark:text-white inline-flex h-10 mb-0 gap-2 font-medium;
|
||||||
|
|
||||||
|
.button__content {
|
||||||
|
@apply w-full whitespace-nowrap overflow-hidden text-ellipsis;
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
@apply inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not(.success):not(.alert):not(.warning):not(
|
||||||
|
.clear
|
||||||
|
):not(.smooth):not(.hollow) {
|
||||||
|
@apply bg-n-brand/80 dark:bg-n-brand/80;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled,
|
||||||
|
&.disabled {
|
||||||
|
@apply opacity-40 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
@apply bg-n-teal-9 text-white dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
@apply bg-n-solid-3 text-white dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
@apply bg-n-brand text-white dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.clear {
|
||||||
|
@apply text-n-blue-text dark:text-n-blue-text bg-transparent dark:bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert {
|
||||||
|
@apply bg-n-ruby-9 text-white dark:text-white;
|
||||||
|
|
||||||
|
&.clear {
|
||||||
|
@apply bg-transparent dark:bg-transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
@apply bg-n-amber-9 text-white dark:text-white;
|
||||||
|
|
||||||
|
&.clear {
|
||||||
|
@apply bg-transparent dark:bg-transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tiny {
|
||||||
|
@apply h-6 text-[10px];
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
@apply h-8 text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
@apply px-2 py-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODDO - Remove after moving all buttons to woot-button
|
||||||
|
.icon + .button__content {
|
||||||
|
@apply w-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
@apply flex justify-center text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.round {
|
||||||
|
@apply rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO Use with link
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
@apply pb-0 pt-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hollow {
|
||||||
|
@apply border border-n-brand/40 bg-transparent text-n-blue-text hover:enabled:bg-n-brand/20;
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
@apply text-n-slate-12 border-n-slate-5 hover:enabled:bg-n-slate-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
@apply text-n-teal-9 border-n-teal-8 hover:enabled:bg-n-teal-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert {
|
||||||
|
@apply text-n-ruby-9 border-n-ruby-8 hover:enabled:bg-n-ruby-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
@apply text-n-amber-9 border-n-amber-8 hover:enabled:bg-n-amber-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smooth style
|
||||||
|
&.smooth {
|
||||||
|
@apply bg-n-brand/10 dark:bg-n-brand/30 text-n-blue-text hover:enabled:bg-n-brand/20 dark:hover:enabled:bg-n-brand/40;
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
@apply bg-n-slate-4 text-n-slate-11 hover:enabled:text-n-slate-11 hover:enabled:bg-n-slate-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
@apply bg-n-teal-4 text-n-teal-11 hover:enabled:text-n-teal-11 hover:enabled:bg-n-teal-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert {
|
||||||
|
@apply bg-n-ruby-4 text-n-ruby-11 hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
@apply bg-n-amber-4 text-n-amber-11 hover:enabled:text-n-amber-11 hover:enabled:bg-n-amber-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.clear {
|
||||||
|
@apply text-n-blue-text hover:enabled:bg-n-brand/10 dark:hover:enabled:bg-n-brand/30;
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
@apply text-n-slate-12 hover:enabled:bg-n-slate-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
@apply text-n-teal-10 hover:enabled:bg-n-teal-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert {
|
||||||
|
@apply text-n-ruby-11 hover:enabled:bg-n-ruby-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
@apply text-n-amber-11 hover:enabled:bg-n-amber-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
&.secondary {
|
||||||
|
@apply active:bg-n-slate-3 dark:active:bg-n-slate-7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
&.secondary {
|
||||||
|
@apply focus:bg-n-slate-4 dark:focus:bg-n-slate-6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
&.tiny {
|
||||||
|
@apply h-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
@apply h-8 pb-1 pt-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
@apply h-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button--only-icon {
|
||||||
|
@apply justify-center pl-0 pr-0 w-10;
|
||||||
|
|
||||||
|
&.tiny {
|
||||||
|
@apply w-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
@apply w-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
@apply w-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.link {
|
||||||
|
@apply h-auto m-0 p-0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
<!-- DEPRECIATED -->
|
|
||||||
<!-- TODO: Replace this banner component with NextBanner "app/javascript/dashboard/components-next/banner/Banner.vue" -->
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export default {
|
|||||||
{{ $t('FILTER.CUSTOM_VIEWS.ADD.TITLE') }}
|
{{ $t('FILTER.CUSTOM_VIEWS.ADD.TITLE') }}
|
||||||
</h3>
|
</h3>
|
||||||
<form class="w-full grid gap-6" @submit.prevent="saveCustomViews">
|
<form class="w-full grid gap-6" @submit.prevent="saveCustomViews">
|
||||||
<label :class="{ error: v$.name.$error }">
|
<div>
|
||||||
<input
|
<input
|
||||||
v-model="name"
|
v-model="name"
|
||||||
class="py-1.5 px-3 text-n-slate-12 bg-n-alpha-1 text-sm rounded-lg reset-base w-full"
|
class="py-1.5 px-3 text-n-slate-12 bg-n-alpha-1 text-sm rounded-lg reset-base w-full"
|
||||||
@@ -116,14 +116,14 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ $t('FILTER.CUSTOM_VIEWS.ADD.ERROR_MESSAGE') }}
|
{{ $t('FILTER.CUSTOM_VIEWS.ADD.ERROR_MESSAGE') }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</div>
|
||||||
<div class="flex flex-row justify-end w-full gap-2">
|
<div class="flex flex-row justify-end w-full gap-2">
|
||||||
|
<NextButton sm solid blue :disabled="isButtonDisabled">
|
||||||
|
{{ $t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON') }}
|
||||||
|
</NextButton>
|
||||||
<NextButton faded slate sm @click.prevent="onClose">
|
<NextButton faded slate sm @click.prevent="onClose">
|
||||||
{{ $t('FILTER.CUSTOM_VIEWS.ADD.CANCEL_BUTTON') }}
|
{{ $t('FILTER.CUSTOM_VIEWS.ADD.CANCEL_BUTTON') }}
|
||||||
</NextButton>
|
</NextButton>
|
||||||
<NextButton solid blue sm :disabled="isButtonDisabled">
|
|
||||||
{{ $t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON') }}
|
|
||||||
</NextButton>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,12 +26,6 @@ const showAccountSwitcher = computed(
|
|||||||
() => userAccounts.value.length > 1 && currentAccount.value.name
|
() => userAccounts.value.length > 1 && currentAccount.value.name
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedCurrentUserAccounts = computed(() => {
|
|
||||||
return [...(currentUser.value.accounts || [])].sort((a, b) =>
|
|
||||||
a.name.localeCompare(b.name)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const onChangeAccount = newId => {
|
const onChangeAccount = newId => {
|
||||||
const accountUrl = `/app/accounts/${newId}/dashboard`;
|
const accountUrl = `/app/accounts/${newId}/dashboard`;
|
||||||
window.location.href = accountUrl;
|
window.location.href = accountUrl;
|
||||||
@@ -76,7 +70,7 @@ const emitNewAccount = () => {
|
|||||||
<DropdownBody v-if="showAccountSwitcher" class="min-w-80 z-50">
|
<DropdownBody v-if="showAccountSwitcher" class="min-w-80 z-50">
|
||||||
<DropdownSection :title="t('SIDEBAR_ITEMS.SWITCH_ACCOUNT')">
|
<DropdownSection :title="t('SIDEBAR_ITEMS.SWITCH_ACCOUNT')">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
v-for="account in sortedCurrentUserAccounts"
|
v-for="account in currentUser.accounts"
|
||||||
:id="`account-${account.id}`"
|
:id="`account-${account.id}`"
|
||||||
:key="account.id"
|
:key="account.id"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const onToggle = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<button
|
<button
|
||||||
class="flex items-center select-none w-full rounded-lg bg-n-slate-2 outline outline-1 outline-n-weak m-0 cursor-grab justify-between py-2 px-4 drag-handle"
|
class="flex items-center select-none w-full rounded-lg bg-n-slate-2 border border-n-weak m-0 cursor-grab justify-between py-2 px-4 drag-handle"
|
||||||
:class="{ 'rounded-bl-none rounded-br-none': isOpen }"
|
:class="{ 'rounded-bl-none rounded-br-none': isOpen }"
|
||||||
@click.stop="onToggle"
|
@click.stop="onToggle"
|
||||||
>
|
>
|
||||||
@@ -55,7 +55,7 @@ const onToggle = () => {
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-if="isOpen"
|
v-if="isOpen"
|
||||||
class="bg-n-background outline outline-1 outline-n-weak -mt-[-1px] border-t-0 rounded-br-lg rounded-bl-lg"
|
class="bg-n-background border border-n-weak dark:border-n-slate-2 border-t-0 rounded-br-lg rounded-bl-lg"
|
||||||
:class="compact ? 'p-0' : 'px-2 py-4'"
|
:class="compact ? 'p-0' : 'px-2 py-4'"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script>
|
||||||
|
import 'highlight.js/styles/default.css';
|
||||||
|
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||||
|
import { useAlert } from 'dashboard/composables';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
masked: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async onCopy(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
await copyTextToClipboard(this.value);
|
||||||
|
useAlert(this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||||
|
},
|
||||||
|
toggleMasked() {
|
||||||
|
this.masked = !this.masked;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="text--container">
|
||||||
|
<woot-button size="small" class="button--text" @click="onCopy">
|
||||||
|
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
|
variant="clear"
|
||||||
|
size="small"
|
||||||
|
class="button--visibility"
|
||||||
|
color-scheme="secondary"
|
||||||
|
:icon="masked ? 'eye-show' : 'eye-hide'"
|
||||||
|
@click.prevent="toggleMasked"
|
||||||
|
/>
|
||||||
|
<highlightjs v-if="value" :code="masked ? '•'.repeat(10) : value" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.text--container {
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.button--text,
|
||||||
|
.button--visibility {
|
||||||
|
margin-top: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--visibility {
|
||||||
|
right: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,16 +3,12 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
|||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { emitter } from 'shared/helpers/mitt';
|
import { emitter } from 'shared/helpers/mitt';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'sm',
|
default: 'small',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -37,13 +33,13 @@ export default {
|
|||||||
|
|
||||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||||
<template>
|
<template>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="!hasNextSidebar"
|
v-if="!hasNextSidebar"
|
||||||
ghost
|
|
||||||
slate
|
|
||||||
:size="size"
|
:size="size"
|
||||||
icon="i-lucide-menu"
|
variant="clear"
|
||||||
class="-ml-3"
|
color-scheme="secondary"
|
||||||
|
class="-ml-3 text-black-900 dark:text-slate-300"
|
||||||
|
icon="list"
|
||||||
@click="onMenuItemClick"
|
@click="onMenuItemClick"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default {
|
|||||||
color-scheme="alert"
|
color-scheme="alert"
|
||||||
:banner-message="bannerMessage"
|
:banner-message="bannerMessage"
|
||||||
:action-button-label="actionButtonMessage"
|
:action-button-label="actionButtonMessage"
|
||||||
action-button-icon="i-lucide-mail"
|
action-button-icon="mail"
|
||||||
has-action-button
|
has-action-button
|
||||||
@primary-action="resendVerificationEmail"
|
@primary-action="resendVerificationEmail"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<script>
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Spinner,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
buttonIconClass: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'button',
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.warn(
|
||||||
|
'[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button :type="type" class="button nice" :class="variant">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="!isLoading && icon"
|
||||||
|
class="icon"
|
||||||
|
:class="buttonIconClass"
|
||||||
|
:icon="icon"
|
||||||
|
/>
|
||||||
|
<Spinner v-if="isLoading" />
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script>
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Spinner,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
buttonClass: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
spinnerClass: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'submit',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedClass() {
|
||||||
|
return `button nice gap-2 ${this.buttonClass || ' '}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:type="type"
|
||||||
|
data-testid="submit_button"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="computedClass"
|
||||||
|
>
|
||||||
|
<fluent-icon v-if="!!iconClass" :icon="iconClass" class="icon" />
|
||||||
|
<span>{{ buttonText }}</span>
|
||||||
|
<Spinner v-if="loading" class="ml-2" :color-scheme="spinnerClass" />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
button:disabled {
|
||||||
|
@apply bg-woot-100 dark:bg-woot-500/25 dark:text-slate-500 opacity-100;
|
||||||
|
&:hover {
|
||||||
|
@apply bg-woot-100 dark:bg-woot-500/25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -134,7 +134,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative flex items-center justify-end resolve-actions">
|
<div class="relative flex items-center justify-end resolve-actions">
|
||||||
<div
|
<div
|
||||||
class="rounded-lg shadow outline-1 outline"
|
class="rounded-lg shadow button-group outline-1 outline"
|
||||||
:class="!showOpenButton ? 'outline-n-container' : 'outline-transparent'"
|
:class="!showOpenButton ? 'outline-n-container' : 'outline-transparent'"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// [NOTE][DEPRECATED] This method is to be deprecated, please do not add new components to this file.
|
// [NOTE][DEPRECATED] This method is to be deprecated, please do not add new components to this file.
|
||||||
/* eslint no-plusplus: 0 */
|
/* eslint no-plusplus: 0 */
|
||||||
import AvatarUploader from './widgets/forms/AvatarUploader.vue';
|
import AvatarUploader from './widgets/forms/AvatarUploader.vue';
|
||||||
|
import Button from './ui/WootButton.vue';
|
||||||
import Code from './Code.vue';
|
import Code from './Code.vue';
|
||||||
import ColorPicker from './widgets/ColorPicker.vue';
|
import ColorPicker from './widgets/ColorPicker.vue';
|
||||||
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
|
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
|
||||||
@@ -17,6 +18,7 @@ import ModalHeader from './ModalHeader.vue';
|
|||||||
import Modal from './Modal.vue';
|
import Modal from './Modal.vue';
|
||||||
import SidemenuIcon from './SidemenuIcon.vue';
|
import SidemenuIcon from './SidemenuIcon.vue';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
import SubmitButton from './buttons/FormSubmitButton.vue';
|
||||||
import Tabs from './ui/Tabs/Tabs.vue';
|
import Tabs from './ui/Tabs/Tabs.vue';
|
||||||
import TabsItem from './ui/Tabs/TabsItem.vue';
|
import TabsItem from './ui/Tabs/TabsItem.vue';
|
||||||
import Thumbnail from './widgets/Thumbnail.vue';
|
import Thumbnail from './widgets/Thumbnail.vue';
|
||||||
@@ -24,6 +26,7 @@ import DatePicker from './ui/DatePicker/DatePicker.vue';
|
|||||||
|
|
||||||
const WootUIKit = {
|
const WootUIKit = {
|
||||||
AvatarUploader,
|
AvatarUploader,
|
||||||
|
Button,
|
||||||
Code,
|
Code,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
ConfirmDeleteModal,
|
ConfirmDeleteModal,
|
||||||
@@ -40,6 +43,7 @@ const WootUIKit = {
|
|||||||
ModalHeader,
|
ModalHeader,
|
||||||
SidemenuIcon,
|
SidemenuIcon,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
SubmitButton,
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsItem,
|
TabsItem,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue
|
|||||||
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider.vue';
|
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider.vue';
|
||||||
import AvailabilityStatusBadge from '../widgets/conversation/AvailabilityStatusBadge.vue';
|
import AvailabilityStatusBadge from '../widgets/conversation/AvailabilityStatusBadge.vue';
|
||||||
import wootConstants from 'dashboard/constants/globals';
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
const { AVAILABILITY_STATUS_KEYS } = wootConstants;
|
const { AVAILABILITY_STATUS_KEYS } = wootConstants;
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ export default {
|
|||||||
WootDropdownMenu,
|
WootDropdownMenu,
|
||||||
WootDropdownItem,
|
WootDropdownItem,
|
||||||
AvailabilityStatusBadge,
|
AvailabilityStatusBadge,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -103,21 +101,19 @@ export default {
|
|||||||
:key="status.value"
|
:key="status.value"
|
||||||
class="flex items-baseline"
|
class="flex items-baseline"
|
||||||
>
|
>
|
||||||
<NextButton
|
<woot-button
|
||||||
sm
|
size="small"
|
||||||
:color="status.disabled ? 'blue' : 'slate'"
|
:color-scheme="status.disabled ? '' : 'secondary'"
|
||||||
:variant="status.disabled ? 'faded' : 'ghost'"
|
:variant="status.disabled ? 'smooth' : 'clear'"
|
||||||
class="status-change--dropdown-button !w-full !justify-start"
|
class="status-change--dropdown-button"
|
||||||
@click="changeAvailabilityStatus(status.value)"
|
@click="changeAvailabilityStatus(status.value)"
|
||||||
>
|
>
|
||||||
<AvailabilityStatusBadge :status="status.value" />
|
<AvailabilityStatusBadge :status="status.value" />
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
{{ status.label }}
|
||||||
{{ status.label }}
|
</woot-button>
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownDivider />
|
<WootDropdownDivider />
|
||||||
<WootDropdownItem class="flex items-center justify-between px-3 py-2 m-0">
|
<WootDropdownItem class="flex items-center justify-between p-2 m-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<fluent-icon
|
<fluent-icon
|
||||||
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
|
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
|
||||||
@@ -127,7 +123,7 @@ export default {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="mx-2 my-0 text-xs font-medium text-slate-600 dark:text-slate-100"
|
class="mx-1 my-0 text-xs font-medium text-slate-600 dark:text-slate-100"
|
||||||
>
|
>
|
||||||
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
|
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
emits: ['toggleAccounts'],
|
emits: ['toggleAccounts'],
|
||||||
data() {
|
data() {
|
||||||
return { showSwitchButton: false };
|
return { showSwitchButton: false };
|
||||||
@@ -50,13 +46,14 @@ export default {
|
|||||||
class="absolute top-0 right-0 flex items-center justify-end w-full h-full rounded-md ltr:overlay-shadow ltr:dark:overlay-shadow-dark rtl:rtl-overlay-shadow rtl:dark:rtl-overlay-shadow-dark"
|
class="absolute top-0 right-0 flex items-center justify-end w-full h-full rounded-md ltr:overlay-shadow ltr:dark:overlay-shadow-dark rtl:rtl-overlay-shadow rtl:dark:rtl-overlay-shadow-dark"
|
||||||
>
|
>
|
||||||
<div class="mx-2 my-0">
|
<div class="mx-2 my-0">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
variant="clear"
|
||||||
xs
|
size="tiny"
|
||||||
icon="i-lucide-arrow-right-left"
|
icon="arrow-swap"
|
||||||
:label="$t('SIDEBAR.SWITCH')"
|
|
||||||
@click="$emit('toggleAccounts')"
|
@click="$emit('toggleAccounts')"
|
||||||
/>
|
>
|
||||||
|
{{ $t('SIDEBAR.SWITCH') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|||||||
@@ -3,12 +3,8 @@ import { required, minLength } from '@vuelidate/validators';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -90,24 +86,19 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex justify-end gap-2 items-center">
|
<div class="w-full">
|
||||||
<NextButton
|
<div class="w-full">
|
||||||
faded
|
<woot-submit-button
|
||||||
slate
|
:disabled="
|
||||||
type="reset"
|
v$.accountName.$invalid ||
|
||||||
:label="$t('CREATE_ACCOUNT.FORM.CANCEL')"
|
v$.accountName.$invalid ||
|
||||||
@click.prevent="() => $emit('closeAccountCreateModal')"
|
uiFlags.isCreating
|
||||||
/>
|
"
|
||||||
<NextButton
|
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
|
||||||
type="submit"
|
:loading="uiFlags.isCreating"
|
||||||
:label="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
|
button-class="large expanded"
|
||||||
:is-loading="uiFlags.isCreating"
|
/>
|
||||||
:disabled="
|
</div>
|
||||||
v$.accountName.$invalid ||
|
|
||||||
v$.accountName.$invalid ||
|
|
||||||
uiFlags.isCreating
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import Thumbnail from '../../widgets/Thumbnail.vue';
|
import Thumbnail from '../../widgets/Thumbnail.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
emits: ['toggleMenu'],
|
emits: ['toggleMenu'],
|
||||||
computed: {
|
computed: {
|
||||||
@@ -27,10 +25,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-tooltip.right="$t(`SIDEBAR.PROFILE_SETTINGS`)"
|
v-tooltip.right="$t(`SIDEBAR.PROFILE_SETTINGS`)"
|
||||||
link
|
variant="link"
|
||||||
class="rounded-full"
|
class="flex items-center rounded-full"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
>
|
>
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
@@ -39,7 +37,6 @@ export default {
|
|||||||
:status="statusOfAgent"
|
:status="statusOfAgent"
|
||||||
should-show-status-always
|
should-show-status-always
|
||||||
size="32px"
|
size="32px"
|
||||||
class="flex-shrink-0"
|
|
||||||
/>
|
/>
|
||||||
</NextButton>
|
</woot-button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
|||||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||||
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus.vue';
|
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus.vue';
|
||||||
import { FEATURE_FLAGS } from '../../../featureFlags';
|
import { FEATURE_FLAGS } from '../../../featureFlags';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
WootDropdownMenu,
|
WootDropdownMenu,
|
||||||
WootDropdownItem,
|
WootDropdownItem,
|
||||||
AvailabilityStatus,
|
AvailabilityStatus,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
show: {
|
show: {
|
||||||
@@ -84,46 +82,37 @@ export default {
|
|||||||
<AvailabilityStatus />
|
<AvailabilityStatus />
|
||||||
<WootDropdownMenu>
|
<WootDropdownMenu>
|
||||||
<WootDropdownItem v-if="showChangeAccountOption">
|
<WootDropdownItem v-if="showChangeAccountOption">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
variant="clear"
|
||||||
sm
|
color-scheme="secondary"
|
||||||
slate
|
size="small"
|
||||||
icon="i-lucide-arrow-right-left"
|
icon="arrow-swap"
|
||||||
class="!w-full !justify-start"
|
|
||||||
@click="$emit('toggleAccounts')"
|
@click="$emit('toggleAccounts')"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
|
||||||
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
|
</woot-button>
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownItem v-if="showChatSupport">
|
<WootDropdownItem v-if="showChatSupport">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
variant="clear"
|
||||||
sm
|
color-scheme="secondary"
|
||||||
slate
|
size="small"
|
||||||
icon="i-lucide-message-circle-question"
|
icon="chat-help"
|
||||||
class="!w-full !justify-start"
|
|
||||||
@click="$emit('showSupportChatWindow')"
|
@click="$emit('showSupportChatWindow')"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
{{ $t('SIDEBAR_ITEMS.CONTACT_SUPPORT') }}
|
||||||
{{ $t('SIDEBAR_ITEMS.CONTACT_SUPPORT') }}
|
</woot-button>
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownItem>
|
<WootDropdownItem>
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
variant="clear"
|
||||||
sm
|
color-scheme="secondary"
|
||||||
slate
|
size="small"
|
||||||
icon="i-lucide-keyboard"
|
icon="keyboard"
|
||||||
class="!w-full !justify-start"
|
|
||||||
@click="handleKeyboardHelpClick"
|
@click="handleKeyboardHelpClick"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
{{ $t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS') }}
|
||||||
{{ $t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS') }}
|
</woot-button>
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownItem>
|
<WootDropdownItem>
|
||||||
<router-link
|
<router-link
|
||||||
@@ -133,70 +122,56 @@ export default {
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
:href="href"
|
:href="href"
|
||||||
|
class="h-8 bg-white button small clear secondary dark:bg-slate-800"
|
||||||
:class="{ 'is-active': isActive }"
|
:class="{ 'is-active': isActive }"
|
||||||
@click="e => handleProfileSettingClick(e, navigate)"
|
@click="e => handleProfileSettingClick(e, navigate)"
|
||||||
>
|
>
|
||||||
<NextButton
|
<fluent-icon icon="person" size="14" class="icon icon--font" />
|
||||||
ghost
|
<span class="button__content">
|
||||||
sm
|
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
|
||||||
slate
|
</span>
|
||||||
icon="i-lucide-circle-user"
|
|
||||||
class="!w-full !justify-start"
|
|
||||||
>
|
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
|
||||||
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
|
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</a>
|
</a>
|
||||||
</router-link>
|
</router-link>
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownItem>
|
<WootDropdownItem>
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
variant="clear"
|
||||||
sm
|
color-scheme="secondary"
|
||||||
slate
|
size="small"
|
||||||
icon="i-lucide-sun-moon"
|
icon="appearance"
|
||||||
class="!w-full !justify-start"
|
|
||||||
@click="openAppearanceOptions"
|
@click="openAppearanceOptions"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
{{ $t('SIDEBAR_ITEMS.APPEARANCE') }}
|
||||||
{{ $t('SIDEBAR_ITEMS.APPEARANCE') }}
|
</woot-button>
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownItem v-if="currentUser.type === 'SuperAdmin'">
|
<WootDropdownItem v-if="currentUser.type === 'SuperAdmin'">
|
||||||
<a
|
<a
|
||||||
href="/super_admin"
|
href="/super_admin"
|
||||||
|
class="h-8 bg-white button small clear secondary dark:bg-slate-800"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener nofollow noreferrer"
|
rel="noopener nofollow noreferrer"
|
||||||
@click="$emit('close')"
|
@click="$emit('close')"
|
||||||
>
|
>
|
||||||
<NextButton
|
<fluent-icon
|
||||||
ghost
|
icon="content-settings"
|
||||||
sm
|
size="14"
|
||||||
slate
|
class="icon icon--font"
|
||||||
icon="i-lucide-layout-dashboard"
|
/>
|
||||||
class="!w-full !justify-start"
|
<span class="button__content">
|
||||||
>
|
{{ $t('SIDEBAR_ITEMS.SUPER_ADMIN_CONSOLE') }}
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
</span>
|
||||||
{{ $t('SIDEBAR_ITEMS.SUPER_ADMIN_CONSOLE') }}
|
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</a>
|
</a>
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
<WootDropdownItem>
|
<WootDropdownItem>
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
variant="clear"
|
||||||
sm
|
color-scheme="secondary"
|
||||||
slate
|
size="small"
|
||||||
icon="i-lucide-circle-power"
|
icon="power"
|
||||||
class="!w-full !justify-start"
|
|
||||||
@click="logout"
|
@click="logout"
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate font-medium text-xs">
|
{{ $t('SIDEBAR_ITEMS.LOGOUT') }}
|
||||||
{{ $t('SIDEBAR_ITEMS.LOGOUT') }}
|
</woot-button>
|
||||||
</span>
|
|
||||||
</NextButton>
|
|
||||||
</WootDropdownItem>
|
</WootDropdownItem>
|
||||||
</WootDropdownMenu>
|
</WootDropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import {
|
|||||||
isOnUnattendedView,
|
isOnUnattendedView,
|
||||||
} from '../../../store/modules/conversations/helpers/actionHelpers';
|
} from '../../../store/modules/conversations/helpers/actionHelpers';
|
||||||
import Policy from '../../policy.vue';
|
import Policy from '../../policy.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { SecondaryChildNavItem, Policy, NextButton },
|
components: { SecondaryChildNavItem, Policy },
|
||||||
props: {
|
props: {
|
||||||
menuItem: {
|
menuItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -206,7 +205,14 @@ export default {
|
|||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="menuItem.showNewButton" class="flex items-center">
|
<div v-if="menuItem.showNewButton" class="flex items-center">
|
||||||
<NextButton ghost xs slate icon="i-lucide-plus" @click="onClickOpen" />
|
<woot-button
|
||||||
|
size="tiny"
|
||||||
|
variant="clear"
|
||||||
|
color-scheme="secondary"
|
||||||
|
icon="add"
|
||||||
|
class="p-0 ml-2"
|
||||||
|
@click="onClickOpen"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
@@ -266,15 +272,16 @@ export default {
|
|||||||
>
|
>
|
||||||
<li class="pl-1">
|
<li class="pl-1">
|
||||||
<a :href="href">
|
<a :href="href">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
size="tiny"
|
||||||
xs
|
variant="clear"
|
||||||
slate
|
color-scheme="secondary"
|
||||||
icon="i-lucide-plus"
|
icon="add"
|
||||||
:label="$t(`SIDEBAR.${menuItem.newLinkTag}`)"
|
|
||||||
:data-testid="menuItem.dataTestid"
|
:data-testid="menuItem.dataTestid"
|
||||||
@click="e => newLinkClick(e, navigate)"
|
@click="e => newLinkClick(e, navigate)"
|
||||||
/>
|
>
|
||||||
|
{{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
|
||||||
|
</woot-button>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
+1
@@ -41,6 +41,7 @@ describe('AccountSelector', () => {
|
|||||||
'fluent-icon': FluentIcon,
|
'fluent-icon': FluentIcon,
|
||||||
},
|
},
|
||||||
stubs: {
|
stubs: {
|
||||||
|
WootButton: { template: '<button />' },
|
||||||
// override global stub
|
// override global stub
|
||||||
WootModalHeader: false,
|
WootModalHeader: false,
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
import AgentDetails from '../AgentDetails.vue';
|
import AgentDetails from '../AgentDetails.vue';
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
import WootButton from 'dashboard/components/ui/WootButton.vue';
|
||||||
|
|
||||||
describe('AgentDetails', () => {
|
describe('AgentDetails', () => {
|
||||||
const currentUser = {
|
const currentUser = {
|
||||||
@@ -40,12 +40,12 @@ describe('AgentDetails', () => {
|
|||||||
plugins: [store],
|
plugins: [store],
|
||||||
components: {
|
components: {
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
NextButton,
|
WootButton,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
tooltip: mockTooltipDirective, // Mocking the tooltip directive
|
tooltip: mockTooltipDirective, // Mocking the tooltip directive
|
||||||
},
|
},
|
||||||
stubs: { NextButton: { template: '<button><slot /></button>' } },
|
stubs: { WootButton: { template: '<button><slot /></button>' } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
import AvailabilityStatus from '../AvailabilityStatus.vue';
|
import AvailabilityStatus from '../AvailabilityStatus.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
import WootButton from 'dashboard/components/ui/WootButton.vue';
|
||||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||||
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue';
|
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue';
|
||||||
@@ -40,7 +40,7 @@ describe('AvailabilityStatus', () => {
|
|||||||
global: {
|
global: {
|
||||||
plugins: [store],
|
plugins: [store],
|
||||||
components: {
|
components: {
|
||||||
NextButton,
|
WootButton,
|
||||||
WootDropdownItem,
|
WootDropdownItem,
|
||||||
WootDropdownMenu,
|
WootDropdownMenu,
|
||||||
WootDropdownHeader,
|
WootDropdownHeader,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const store = createStore({
|
|||||||
describe('SidemenuIcon', () => {
|
describe('SidemenuIcon', () => {
|
||||||
test('matches snapshot', () => {
|
test('matches snapshot', () => {
|
||||||
const wrapper = shallowMount(SidemenuIcon, {
|
const wrapper = shallowMount(SidemenuIcon, {
|
||||||
stubs: { NextButton: { template: '<button><slot /></button>' } },
|
stubs: { WootButton: { template: '<button><slot /></button>' } },
|
||||||
global: { plugins: [store] },
|
global: { plugins: [store] },
|
||||||
});
|
});
|
||||||
expect(wrapper.vm).toBeTruthy();
|
expect(wrapper.vm).toBeTruthy();
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
exports[`SidemenuIcon > matches snapshot 1`] = `
|
exports[`SidemenuIcon > matches snapshot 1`] = `
|
||||||
<button
|
<button
|
||||||
class="-ml-3"
|
class="-ml-3 text-black-900 dark:text-slate-300"
|
||||||
ghost=""
|
color-scheme="secondary"
|
||||||
icon="i-lucide-menu"
|
icon="list"
|
||||||
size="sm"
|
size="small"
|
||||||
slate=""
|
variant="clear"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
bannerMessage: {
|
bannerMessage: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -24,7 +19,7 @@ export default {
|
|||||||
},
|
},
|
||||||
actionButtonVariant: {
|
actionButtonVariant: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'faded',
|
default: '',
|
||||||
},
|
},
|
||||||
actionButtonLabel: {
|
actionButtonLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -32,7 +27,7 @@ export default {
|
|||||||
},
|
},
|
||||||
actionButtonIcon: {
|
actionButtonIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'i-lucide-arrow-right',
|
default: 'arrow-right',
|
||||||
},
|
},
|
||||||
colorScheme: {
|
colorScheme: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -53,18 +48,6 @@ export default {
|
|||||||
}
|
}
|
||||||
return classList;
|
return classList;
|
||||||
},
|
},
|
||||||
// TODO - Remove this method when we standardize
|
|
||||||
// the button color and variant names
|
|
||||||
getButtonColor() {
|
|
||||||
const colorMap = {
|
|
||||||
primary: 'blue',
|
|
||||||
secondary: 'blue',
|
|
||||||
alert: 'ruby',
|
|
||||||
warning: 'amber',
|
|
||||||
};
|
|
||||||
|
|
||||||
return colorMap[this.colorScheme] || 'blue';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick(e) {
|
onClick(e) {
|
||||||
@@ -94,23 +77,27 @@ export default {
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="hasActionButton"
|
v-if="hasActionButton"
|
||||||
xs
|
size="tiny"
|
||||||
:icon="actionButtonIcon"
|
:icon="actionButtonIcon"
|
||||||
:variant="actionButtonVariant"
|
:variant="actionButtonVariant"
|
||||||
:color="getButtonColor"
|
color-scheme="primary"
|
||||||
:label="actionButtonLabel"
|
class-names="banner-action__button"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
/>
|
>
|
||||||
<NextButton
|
{{ actionButtonLabel }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
v-if="hasCloseButton"
|
v-if="hasCloseButton"
|
||||||
xs
|
size="tiny"
|
||||||
icon="i-lucide-circle-x"
|
:color-scheme="colorScheme"
|
||||||
:color="getButtonColor"
|
icon="dismiss-circle"
|
||||||
:label="$t('GENERAL_SETTINGS.DISMISS')"
|
class-names="banner-action__button"
|
||||||
@click="onClickClose"
|
@click="onClickClose"
|
||||||
/>
|
>
|
||||||
|
{{ $t('GENERAL_SETTINGS.DISMISS') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -119,6 +106,13 @@ export default {
|
|||||||
.banner {
|
.banner {
|
||||||
&.primary {
|
&.primary {
|
||||||
@apply bg-woot-500 dark:bg-woot-500;
|
@apply bg-woot-500 dark:bg-woot-500;
|
||||||
|
.banner-action__button {
|
||||||
|
@apply bg-woot-600 dark:bg-woot-600 border-none text-white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-woot-700 dark:bg-woot-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
@@ -130,6 +124,13 @@ export default {
|
|||||||
|
|
||||||
&.alert {
|
&.alert {
|
||||||
@apply bg-n-ruby-3 text-n-ruby-12;
|
@apply bg-n-ruby-3 text-n-ruby-12;
|
||||||
|
.banner-action__button {
|
||||||
|
@apply border-none text-n-ruby-12 bg-n-ruby-5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-n-ruby-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@apply text-n-ruby-12;
|
@apply text-n-ruby-12;
|
||||||
@@ -145,12 +146,21 @@ export default {
|
|||||||
|
|
||||||
&.gray {
|
&.gray {
|
||||||
@apply text-black-500 dark:text-black-500;
|
@apply text-black-500 dark:text-black-500;
|
||||||
|
.banner-action__button {
|
||||||
|
@apply text-white dark:text-white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@apply ml-1 underline text-white dark:text-white text-xs;
|
@apply ml-1 underline text-white dark:text-white text-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.banner-action__button {
|
||||||
|
::v-deep .button__content {
|
||||||
|
@apply whitespace-nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.banner-message {
|
.banner-message {
|
||||||
@apply flex items-center;
|
@apply flex items-center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Button from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
buttonText: {
|
buttonText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
trailingIcon: {
|
rightIcon: {
|
||||||
type: Boolean,
|
type: String,
|
||||||
default: false,
|
default: '',
|
||||||
},
|
},
|
||||||
icon: {
|
leftIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
@@ -18,15 +16,32 @@ defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Button
|
<button
|
||||||
ghost
|
class="inline-flex relative items-center p-1.5 w-fit h-8 gap-1.5 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 active:bg-slate-75 dark:active:bg-slate-800"
|
||||||
slate
|
|
||||||
sm
|
|
||||||
class="relative"
|
|
||||||
:icon="icon"
|
|
||||||
:trailing-icon="trailingIcon"
|
|
||||||
>
|
>
|
||||||
<span class="min-w-0 truncate">{{ buttonText }}</span>
|
<slot name="leftIcon">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="leftIcon"
|
||||||
|
:icon="leftIcon"
|
||||||
|
size="18"
|
||||||
|
class="flex-shrink-0 text-slate-900 dark:text-slate-50"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
<span
|
||||||
|
v-if="buttonText"
|
||||||
|
class="text-sm font-medium truncate text-slate-900 dark:text-slate-50"
|
||||||
|
>
|
||||||
|
{{ buttonText }}
|
||||||
|
</span>
|
||||||
|
<slot name="rightIcon">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="rightIcon"
|
||||||
|
:icon="rightIcon"
|
||||||
|
size="18"
|
||||||
|
class="flex-shrink-0 text-slate-900 dark:text-slate-50"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
|
||||||
<slot name="dropdown" />
|
<slot name="dropdown" />
|
||||||
</Button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-center h-10 text-sm text-n-slate-11">
|
<div
|
||||||
|
class="flex items-center justify-center h-10 text-sm text-slate-500 dark:text-slate-300"
|
||||||
|
>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const shouldShowEmptyState = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="absolute z-20 w-40 bg-n-solid-2 border-0 outline outline-1 outline-n-weak shadow rounded-xl max-h-[400px]"
|
class="absolute z-20 w-40 bg-white border shadow dark:bg-slate-800 rounded-xl border-slate-50 dark:border-slate-700/50 max-h-[400px]"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<slot name="search">
|
<slot name="search">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ defineProps({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="relative inline-flex items-center justify-start w-full p-3 border-0 rounded-none first:rounded-t-xl last:rounded-b-xl h-11 hover:enabled:bg-n-alpha-2"
|
class="relative inline-flex items-center justify-start w-full p-3 border-0 rounded-none first:rounded-t-xl last:rounded-b-xl h-11 hover:bg-slate-50 dark:hover:bg-slate-700 active:bg-slate-75 dark:active:bg-slate-800"
|
||||||
>
|
>
|
||||||
<div class="inline-flex items-center gap-3 overflow-hidden">
|
<div class="inline-flex items-center gap-3 overflow-hidden">
|
||||||
<fluent-icon
|
<fluent-icon
|
||||||
@@ -30,14 +30,16 @@ defineProps({
|
|||||||
size="18"
|
size="18"
|
||||||
:style="{ color: iconColor }"
|
:style="{ color: iconColor }"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm font-medium truncate text-n-slate-12">
|
<span
|
||||||
|
class="text-sm font-medium truncate text-slate-900 dark:text-slate-50"
|
||||||
|
>
|
||||||
{{ buttonText }}
|
{{ buttonText }}
|
||||||
</span>
|
</span>
|
||||||
<fluent-icon
|
<fluent-icon
|
||||||
v-if="isActive"
|
v-if="isActive"
|
||||||
icon="checkmark"
|
icon="checkmark"
|
||||||
size="18"
|
size="18"
|
||||||
class="flex-shrink-0 text-n-slate-12"
|
class="flex-shrink-0 text-slate-900 dark:text-slate-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<slot name="dropdown" />
|
<slot name="dropdown" />
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-center h-10 text-sm text-n-slate-11">
|
<div
|
||||||
|
class="flex items-center justify-center h-10 text-sm text-slate-500 dark:text-slate-300"
|
||||||
|
>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineEmits, defineModel } from 'vue';
|
import { defineEmits, defineModel } from 'vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
inputPlaceholder: {
|
inputPlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -23,29 +21,31 @@ const value = defineModel({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between h-10 min-h-[40px] sticky top-0 bg-n-solid-2 dark:bg-n-solid-2 z-10 gap-2 px-3 border-b rounded-t-xl border-n-weak"
|
class="flex items-center justify-between h-10 min-h-[40px] sticky top-0 bg-white z-10 dark:bg-slate-800 gap-2 px-3 border-b rounded-t-xl border-slate-50 dark:border-slate-700"
|
||||||
>
|
>
|
||||||
<div class="flex items-center w-full gap-2" @keyup.space.prevent>
|
<div class="flex items-center w-full gap-2" @keyup.space.prevent>
|
||||||
<fluent-icon
|
<fluent-icon
|
||||||
icon="search"
|
icon="search"
|
||||||
size="16"
|
size="16"
|
||||||
class="text-n-slate-11 flex-shrink-0"
|
class="text-slate-400 dark:text-slate-400 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-model="value"
|
v-model="value"
|
||||||
:placeholder="inputPlaceholder"
|
:placeholder="inputPlaceholder"
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full mb-0 text-sm !outline-0 bg-transparent text-n-slate-12 placeholder:text-n-slate-10 reset-base"
|
class="w-full mb-0 text-sm bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-75 reset-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- Clear filter button -->
|
<!-- Clear filter button -->
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="!modelValue && showClearFilter"
|
v-if="!modelValue && showClearFilter"
|
||||||
faded
|
size="small"
|
||||||
xs
|
variant="clear"
|
||||||
class="flex-shrink-0"
|
color-scheme="primary"
|
||||||
:label="$t('REPORT.FILTER_ACTIONS.CLEAR_FILTER')"
|
class="!px-1 !py-1.5"
|
||||||
@click="emit('remove')"
|
@click="emit('remove')"
|
||||||
/>
|
>
|
||||||
|
{{ $t('REPORT.FILTER_ACTIONS.CLEAR_FILTER') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<script>
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WootButton',
|
||||||
|
components: { EmojiOrIcon, Spinner },
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'submit',
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
emoji: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary',
|
||||||
|
},
|
||||||
|
classNames: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isExpanded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
variantClasses() {
|
||||||
|
if (this.variant.includes('link')) {
|
||||||
|
return `clear ${this.variant}`;
|
||||||
|
}
|
||||||
|
return this.variant;
|
||||||
|
},
|
||||||
|
hasOnlyIcon() {
|
||||||
|
const hasEmojiOrIcon = this.emoji || this.icon;
|
||||||
|
return !this.$slots.default && hasEmojiOrIcon;
|
||||||
|
},
|
||||||
|
hasOnlyIconClasses() {
|
||||||
|
return this.hasOnlyIcon ? 'button--only-icon' : '';
|
||||||
|
},
|
||||||
|
buttonClasses() {
|
||||||
|
return [
|
||||||
|
this.variantClasses,
|
||||||
|
this.hasOnlyIconClasses,
|
||||||
|
this.size,
|
||||||
|
this.colorScheme,
|
||||||
|
this.classNames,
|
||||||
|
this.isDisabled ? 'disabled' : '',
|
||||||
|
this.isExpanded ? 'expanded' : '',
|
||||||
|
];
|
||||||
|
},
|
||||||
|
iconSize() {
|
||||||
|
switch (this.size) {
|
||||||
|
case 'tiny':
|
||||||
|
return 12;
|
||||||
|
case 'small':
|
||||||
|
return 14;
|
||||||
|
case 'medium':
|
||||||
|
return 16;
|
||||||
|
case 'large':
|
||||||
|
return 18;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showDarkSpinner() {
|
||||||
|
return (
|
||||||
|
this.colorScheme === 'secondary' ||
|
||||||
|
this.variant === 'clear' ||
|
||||||
|
this.variant === 'link' ||
|
||||||
|
this.variant === 'hollow'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
:type="type"
|
||||||
|
:class="buttonClasses"
|
||||||
|
:disabled="isDisabled || isLoading"
|
||||||
|
>
|
||||||
|
<Spinner
|
||||||
|
v-if="isLoading"
|
||||||
|
size="small"
|
||||||
|
:color-scheme="showDarkSpinner ? 'dark' : ''"
|
||||||
|
/>
|
||||||
|
<EmojiOrIcon
|
||||||
|
v-else-if="icon || emoji"
|
||||||
|
class="icon"
|
||||||
|
:emoji="emoji"
|
||||||
|
:icon="icon"
|
||||||
|
:icon-size="iconSize"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="$slots.default"
|
||||||
|
class="button__content"
|
||||||
|
:class="{ 'text-left rtl:text-right': size !== 'expanded' }"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
|
// Props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentPage: {
|
currentPage: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -21,6 +21,13 @@ const hasFirstPage = computed(() => props.currentPage === 1);
|
|||||||
const hasNextPage = computed(() => props.currentPage === props.totalPages);
|
const hasNextPage = computed(() => props.currentPage === props.totalPages);
|
||||||
const hasPrevPage = computed(() => props.currentPage === 1);
|
const hasPrevPage = computed(() => props.currentPage === 1);
|
||||||
|
|
||||||
|
function buttonClass(hasPage) {
|
||||||
|
if (hasPage) {
|
||||||
|
return 'hover:!bg-slate-50 dark:hover:!bg-slate-800';
|
||||||
|
}
|
||||||
|
return 'dark:hover:!bg-slate-700/50';
|
||||||
|
}
|
||||||
|
|
||||||
function onPageChange(newPage) {
|
function onPageChange(newPage) {
|
||||||
emit('pageChange', newPage);
|
emit('pageChange', newPage);
|
||||||
}
|
}
|
||||||
@@ -48,61 +55,84 @@ const onLastPage = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex items-center h-8 rounded-lg bg-slate-50 dark:bg-slate-800">
|
||||||
class="flex items-center h-8 outline outline-1 outline-n-weak rounded-lg"
|
<woot-button
|
||||||
>
|
size="small"
|
||||||
<NextButton
|
variant="smooth"
|
||||||
faded
|
color-scheme="secondary"
|
||||||
sm
|
:is-disabled="hasFirstPage"
|
||||||
slate
|
class-names="dark:!bg-slate-800 !opacity-100 ltr:rounded-l-lg ltr:rounded-r-none rtl:rounded-r-lg rtl:rounded-l-none"
|
||||||
icon="i-lucide-chevrons-left"
|
:class="buttonClass(hasFirstPage)"
|
||||||
class="ltr:rounded-l-lg ltr:rounded-r-none rtl:rounded-r-lg rtl:rounded-l-none"
|
|
||||||
:disabled="hasFirstPage"
|
|
||||||
@click="onFirstPage"
|
@click="onFirstPage"
|
||||||
/>
|
|
||||||
<div class="flex items-center justify-center bg-n-slate-9/10 h-full">
|
|
||||||
<div class="w-px h-4 rounded-sm bg-n-strong" />
|
|
||||||
</div>
|
|
||||||
<NextButton
|
|
||||||
faded
|
|
||||||
sm
|
|
||||||
slate
|
|
||||||
icon="i-lucide-chevron-left"
|
|
||||||
class="rounded-none"
|
|
||||||
:disabled="hasPrevPage"
|
|
||||||
@click="onPrevPage"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-3 px-3 tabular-nums bg-n-slate-9/10 h-full"
|
|
||||||
>
|
>
|
||||||
<span class="text-sm text-n-slate-12">
|
<fluent-icon
|
||||||
|
icon="chevrons-left"
|
||||||
|
size="20"
|
||||||
|
icon-lib="lucide"
|
||||||
|
:class="hasFirstPage && 'opacity-40'"
|
||||||
|
/>
|
||||||
|
</woot-button>
|
||||||
|
<div class="w-px h-4 rounded-sm bg-slate-75 dark:bg-slate-700/50" />
|
||||||
|
<woot-button
|
||||||
|
size="small"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
:is-disabled="hasPrevPage"
|
||||||
|
class-names="dark:!bg-slate-800 !opacity-100 rounded-none"
|
||||||
|
:class="buttonClass(hasPrevPage)"
|
||||||
|
@click="onPrevPage"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
icon="chevron-left-single"
|
||||||
|
size="20"
|
||||||
|
icon-lib="lucide"
|
||||||
|
:class="hasPrevPage && 'opacity-40'"
|
||||||
|
/>
|
||||||
|
</woot-button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-3 px-3 tabular-nums bg-slate-50 dark:bg-slate-800 text-slate-700 dark:text-slate-100"
|
||||||
|
>
|
||||||
|
<span class="text-sm text-slate-800 dark:text-slate-75">
|
||||||
{{ currentPage }}
|
{{ currentPage }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-n-slate-11">/</span>
|
<span class="text-slate-600 dark:text-slate-500">/</span>
|
||||||
<span class="text-sm text-n-slate-11">
|
<span class="text-sm text-slate-600 dark:text-slate-500">
|
||||||
{{ totalPages }}
|
{{ totalPages }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<NextButton
|
<woot-button
|
||||||
faded
|
size="small"
|
||||||
sm
|
variant="smooth"
|
||||||
slate
|
color-scheme="secondary"
|
||||||
icon="i-lucide-chevron-right"
|
:is-disabled="hasNextPage"
|
||||||
class="rounded-none"
|
class-names="dark:!bg-slate-800 !opacity-100 rounded-none"
|
||||||
:disabled="hasNextPage"
|
:class="buttonClass(hasNextPage)"
|
||||||
@click="onNextPage"
|
@click="onNextPage"
|
||||||
/>
|
>
|
||||||
<div class="flex items-center justify-center bg-n-slate-9/10 h-full">
|
<fluent-icon
|
||||||
<div class="w-px h-4 rounded-sm bg-n-strong" />
|
icon="chevron-right-single"
|
||||||
</div>
|
size="20"
|
||||||
<NextButton
|
icon-lib="lucide"
|
||||||
faded
|
:class="hasNextPage && 'opacity-40'"
|
||||||
sm
|
/>
|
||||||
slate
|
</woot-button>
|
||||||
icon="i-lucide-chevrons-right"
|
<div class="w-px h-4 rounded-sm bg-slate-75 dark:bg-slate-700/50" />
|
||||||
class="ltr:rounded-r-lg ltr:rounded-l-none rtl:rounded-l-lg rtl:rounded-r-none"
|
<woot-button
|
||||||
:disabled="hasLastPage"
|
size="small"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
class-names="dark:!bg-slate-800 !opacity-100 ltr:rounded-r-lg ltr:rounded-l-none rtl:rounded-l-lg rtl:rounded-r-none"
|
||||||
|
:class="buttonClass(hasLastPage)"
|
||||||
|
:is-disabled="hasLastPage"
|
||||||
@click="onLastPage"
|
@click="onLastPage"
|
||||||
/>
|
>
|
||||||
|
<fluent-icon
|
||||||
|
icon="chevrons-right"
|
||||||
|
size="20"
|
||||||
|
icon-lib="lucide"
|
||||||
|
:class="hasLastPage && 'opacity-40'"
|
||||||
|
/>
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -106,3 +106,43 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.button-group {
|
||||||
|
@apply flex border-0 p-0 m-0;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
@apply text-sm font-medium py-2.5 px-4 m-0 relative z-10;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
@apply bg-white dark:bg-slate-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--reply {
|
||||||
|
@apply border-r rounded-none border-b-0 border-l-0 border-t-0 border-slate-50 dark:border-slate-700;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
@apply border-r border-slate-50 dark:border-slate-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--note {
|
||||||
|
@apply border-l-0 rounded-none;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
@apply border-r border-b-0 bg-yellow-100 dark:bg-yellow-800 border-t-0 border-slate-50 dark:border-slate-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
@apply text-yellow-700 dark:text-yellow-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--note {
|
||||||
|
@apply text-yellow-600 dark:text-yellow-600 bg-transparent dark:bg-transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -2,12 +2,8 @@
|
|||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required, minLength, email } from '@vuelidate/validators';
|
import { required, minLength, email } from '@vuelidate/validators';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -157,18 +153,13 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||||
<NextButton
|
<woot-submit-button
|
||||||
faded
|
:button-text="$t('EMAIL_TRANSCRIPT.SUBMIT')"
|
||||||
slate
|
|
||||||
type="reset"
|
|
||||||
:label="$t('EMAIL_TRANSCRIPT.CANCEL')"
|
|
||||||
@click.prevent="onCancel"
|
|
||||||
/>
|
|
||||||
<NextButton
|
|
||||||
type="submit"
|
|
||||||
:label="$t('EMAIL_TRANSCRIPT.SUBMIT')"
|
|
||||||
:disabled="!isFormValid"
|
:disabled="!isFormValid"
|
||||||
/>
|
/>
|
||||||
|
<button class="button clear" @click.prevent="onCancel">
|
||||||
|
{{ $t('EMAIL_TRANSCRIPT.CANCEL') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -420,7 +420,6 @@ export default {
|
|||||||
if (conversationId !== oldConversationId) {
|
if (conversationId !== oldConversationId) {
|
||||||
this.setToDraft(oldConversationId, this.replyType);
|
this.setToDraft(oldConversationId, this.replyType);
|
||||||
this.getFromDraft();
|
this.getFromDraft();
|
||||||
this.resetRecorderAndClearAttachments();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
message(updatedMessage) {
|
message(updatedMessage) {
|
||||||
@@ -530,12 +529,6 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetRecorderAndClearAttachments() {
|
|
||||||
// Reset audio recorder UI state
|
|
||||||
this.resetAudioRecorderInput();
|
|
||||||
// Reset attached files
|
|
||||||
this.attachedFiles = [];
|
|
||||||
},
|
|
||||||
saveDraft(conversationId, replyType) {
|
saveDraft(conversationId, replyType) {
|
||||||
if (this.message || this.message === '') {
|
if (this.message || this.message === '') {
|
||||||
const key = `draft-${conversationId}-${replyType}`;
|
const key = `draft-${conversationId}-${replyType}`;
|
||||||
@@ -1074,7 +1067,7 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<Banner
|
<Banner
|
||||||
v-if="showSelfAssignBanner"
|
v-if="showSelfAssignBanner"
|
||||||
action-button-variant="ghost"
|
action-button-variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
class="banner--self-assign mx-2 mb-2 rounded-lg"
|
class="banner--self-assign mx-2 mb-2 rounded-lg"
|
||||||
:banner-message="$t('CONVERSATION.NOT_ASSIGNED_TO_YOU')"
|
:banner-message="$t('CONVERSATION.NOT_ASSIGNED_TO_YOU')"
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import {
|
|||||||
DuplicateContactException,
|
DuplicateContactException,
|
||||||
ExceptionWithMessage,
|
ExceptionWithMessage,
|
||||||
} from 'shared/helpers/CustomErrors';
|
} from 'shared/helpers/CustomErrors';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -95,12 +91,9 @@ export default {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="formattedPhoneNumber" class="link-wrap">
|
<div v-if="formattedPhoneNumber" class="link-wrap">
|
||||||
<NextButton
|
<woot-button variant="clear" size="small" @click.prevent="addContact">
|
||||||
ghost
|
{{ $t('CONVERSATION.SAVE_CONTACT') }}
|
||||||
xs
|
</woot-button>
|
||||||
:label="$t('CONVERSATION.SAVE_CONTACT')"
|
|
||||||
@click.prevent="addContact"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+17
-15
@@ -2,12 +2,8 @@
|
|||||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||||
import { buildDyteURL } from 'shared/helpers/IntegrationHelper';
|
import { buildDyteURL } from 'shared/helpers/IntegrationHelper';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
messageId: {
|
messageId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -45,25 +41,31 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NextButton
|
<woot-button
|
||||||
blue
|
size="small"
|
||||||
sm
|
variant="smooth"
|
||||||
icon="i-lucide-video"
|
color-scheme="secondary"
|
||||||
:label="$t('INTEGRATION_SETTINGS.DYTE.CLICK_HERE_TO_JOIN')"
|
icon="video-add"
|
||||||
|
class="join-call-button"
|
||||||
:is-loading="isLoading"
|
:is-loading="isLoading"
|
||||||
@click="joinTheCall"
|
@click="joinTheCall"
|
||||||
/>
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.DYTE.CLICK_HERE_TO_JOIN') }}
|
||||||
|
</woot-button>
|
||||||
<div v-if="dyteAuthToken" class="video-call--container">
|
<div v-if="dyteAuthToken" class="video-call--container">
|
||||||
<iframe
|
<iframe
|
||||||
:src="meetingLink"
|
:src="meetingLink"
|
||||||
allow="camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;"
|
allow="camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<woot-button
|
||||||
sm
|
size="small"
|
||||||
class="mt-2"
|
variant="smooth"
|
||||||
:label="$t('INTEGRATION_SETTINGS.DYTE.LEAVE_THE_ROOM')"
|
color-scheme="secondary"
|
||||||
|
class="join-call-button"
|
||||||
@click="leaveTheRoom"
|
@click="leaveTheRoom"
|
||||||
/>
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.DYTE.LEAVE_THE_ROOM') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+23
-25
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
// components
|
// components
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
import WootButton from '../../../ui/WootButton.vue';
|
||||||
import Avatar from '../../Avatar.vue';
|
import Avatar from '../../Avatar.vue';
|
||||||
|
|
||||||
// composables
|
// composables
|
||||||
@@ -19,7 +19,7 @@ export default {
|
|||||||
name: 'LabelSuggestion',
|
name: 'LabelSuggestion',
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
Avatar,
|
||||||
NextButton,
|
WootButton,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
suggestedLabels: {
|
suggestedLabels: {
|
||||||
@@ -185,44 +185,42 @@ export default {
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<NextButton
|
<WootButton
|
||||||
v-if="preparedLabels.length === 1"
|
v-if="preparedLabels.length === 1"
|
||||||
v-tooltip.top="{
|
v-tooltip.top="{
|
||||||
content: $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DISMISS'),
|
content: $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DISMISS'),
|
||||||
delay: { show: 600, hide: 0 },
|
delay: { show: 600, hide: 0 },
|
||||||
hideOnClick: true,
|
hideOnClick: true,
|
||||||
}"
|
}"
|
||||||
faded
|
variant="smooth"
|
||||||
xs
|
:color-scheme="isHovered ? 'alert' : 'primary'"
|
||||||
icon="i-lucide-x"
|
class="label--add"
|
||||||
class="flex-shrink-0"
|
icon="dismiss"
|
||||||
:color="isHovered ? 'ruby' : 'blue'"
|
size="tiny"
|
||||||
@click="dismissSuggestions"
|
@click="dismissSuggestions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="preparedLabels.length > 1">
|
||||||
v-if="preparedLabels.length > 1"
|
<WootButton
|
||||||
class="inline-flex items-center gap-1"
|
:variant="selectedLabels.length === 0 ? 'smooth' : ''"
|
||||||
>
|
class="label--add"
|
||||||
<NextButton
|
icon="add"
|
||||||
xs
|
size="tiny"
|
||||||
icon="i-lucide-plus"
|
|
||||||
class="flex-shrink-0"
|
|
||||||
:variant="selectedLabels.length === 0 ? 'faded' : 'solid'"
|
|
||||||
:label="addButtonText"
|
|
||||||
@click="addAllLabels"
|
@click="addAllLabels"
|
||||||
/>
|
>
|
||||||
<NextButton
|
{{ addButtonText }}
|
||||||
|
</WootButton>
|
||||||
|
<WootButton
|
||||||
v-tooltip.top="{
|
v-tooltip.top="{
|
||||||
content: $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DISMISS'),
|
content: $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DISMISS'),
|
||||||
delay: { show: 600, hide: 0 },
|
delay: { show: 600, hide: 0 },
|
||||||
hideOnClick: true,
|
hideOnClick: true,
|
||||||
}"
|
}"
|
||||||
faded
|
:color-scheme="isHovered ? 'alert' : 'primary'"
|
||||||
xs
|
variant="smooth"
|
||||||
icon="i-lucide-x"
|
class="label--add"
|
||||||
class="flex-shrink-0"
|
icon="dismiss"
|
||||||
:color="isHovered ? 'ruby' : 'blue'"
|
size="tiny"
|
||||||
@click="dismissSuggestions"
|
@click="dismissSuggestions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -107,10 +107,9 @@ const linkIssue = async () => {
|
|||||||
:class="shouldShowDropdown ? 'h-[256px]' : 'gap-2'"
|
:class="shouldShowDropdown ? 'h-[256px]' : 'gap-2'"
|
||||||
>
|
>
|
||||||
<FilterButton
|
<FilterButton
|
||||||
trailing-icon
|
right-icon="chevron-down"
|
||||||
icon="i-lucide-chevron-down"
|
|
||||||
:button-text="linkIssueTitle"
|
:button-text="linkIssueTitle"
|
||||||
class="justify-between w-full h-[2.5rem] py-1.5 px-3 rounded-xl bg-n-alpha-black2 outline outline-1 outline-n-weak dark:outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6"
|
class="justify-between w-full h-[2.5rem] py-1.5 px-3 rounded-xl border border-slate-50 bg-slate-25 dark:border-slate-600 dark:bg-slate-900 hover:bg-slate-50 dark:hover:bg-slate-900/50"
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
>
|
>
|
||||||
<template v-if="shouldShowDropdown" #dropdown>
|
<template v-if="shouldShowDropdown" #dropdown>
|
||||||
|
|||||||
+2
-3
@@ -50,10 +50,9 @@ const selectedItemId = computed(() => selectedItem.value?.id || null);
|
|||||||
<label class="w-full" :class="{ error: hasError }">
|
<label class="w-full" :class="{ error: hasError }">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<FilterButton
|
<FilterButton
|
||||||
trailing-icon
|
right-icon="chevron-down"
|
||||||
icon="i-lucide-chevron-down"
|
|
||||||
:button-text="selectedItemName"
|
:button-text="selectedItemName"
|
||||||
class="justify-between w-full h-[2.5rem] py-1.5 px-3 rounded-xl bg-n-alpha-black2 outline outline-1 outline-n-weak dark:outline-n-weak hover:outline-n-slate-6 dark:hover:outline-n-slate-6"
|
class="justify-between w-full h-[2.5rem] py-1.5 px-3 rounded-xl border border-slate-50 bg-slate-25 dark:border-slate-600 dark:bg-slate-900 hover:bg-slate-50 dark:hover:bg-slate-900/50"
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
>
|
>
|
||||||
<template v-if="shouldShowDropdown" #dropdown>
|
<template v-if="shouldShowDropdown" #dropdown>
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -54,13 +49,15 @@ export default {
|
|||||||
:username="usernameAvatar"
|
:username="usernameAvatar"
|
||||||
/>
|
/>
|
||||||
<div v-if="src && deleteAvatar" class="avatar-delete-btn">
|
<div v-if="src && deleteAvatar" class="avatar-delete-btn">
|
||||||
<NextButton
|
<woot-button
|
||||||
outline
|
color-scheme="alert"
|
||||||
xs
|
variant="hollow"
|
||||||
ruby
|
size="tiny"
|
||||||
:label="$t('INBOX_MGMT.DELETE.AVATAR_DELETE_BUTTON_TEXT')"
|
type="button"
|
||||||
@click="onAvatarDelete"
|
@click="onAvatarDelete"
|
||||||
/>
|
>
|
||||||
|
{{ $t('INBOX_MGMT.DELETE.AVATAR_DELETE_BUTTON_TEXT') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export const INBOX_TYPES = {
|
|||||||
TELEGRAM: 'Channel::Telegram',
|
TELEGRAM: 'Channel::Telegram',
|
||||||
LINE: 'Channel::Line',
|
LINE: 'Channel::Line',
|
||||||
SMS: 'Channel::Sms',
|
SMS: 'Channel::Sms',
|
||||||
INSTAGRAM: 'Channel::Instagram',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const INBOX_ICON_MAP_FILL = {
|
const INBOX_ICON_MAP_FILL = {
|
||||||
@@ -21,7 +20,6 @@ const INBOX_ICON_MAP_FILL = {
|
|||||||
[INBOX_TYPES.EMAIL]: 'i-ri-mail-fill',
|
[INBOX_TYPES.EMAIL]: 'i-ri-mail-fill',
|
||||||
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-fill',
|
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-fill',
|
||||||
[INBOX_TYPES.LINE]: 'i-ri-line-fill',
|
[INBOX_TYPES.LINE]: 'i-ri-line-fill',
|
||||||
[INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-fill',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_ICON_FILL = 'i-ri-chat-1-fill';
|
const DEFAULT_ICON_FILL = 'i-ri-chat-1-fill';
|
||||||
@@ -35,7 +33,6 @@ const INBOX_ICON_MAP_LINE = {
|
|||||||
[INBOX_TYPES.EMAIL]: 'i-ri-mail-line',
|
[INBOX_TYPES.EMAIL]: 'i-ri-mail-line',
|
||||||
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-line',
|
[INBOX_TYPES.TELEGRAM]: 'i-ri-telegram-line',
|
||||||
[INBOX_TYPES.LINE]: 'i-ri-line-line',
|
[INBOX_TYPES.LINE]: 'i-ri-line-line',
|
||||||
[INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-line',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_ICON_LINE = 'i-ri-chat-1-line';
|
const DEFAULT_ICON_LINE = 'i-ri-chat-1-line';
|
||||||
@@ -121,9 +118,6 @@ export const getInboxClassByType = (type, phoneNumber) => {
|
|||||||
case INBOX_TYPES.LINE:
|
case INBOX_TYPES.LINE:
|
||||||
return 'brand-line';
|
return 'brand-line';
|
||||||
|
|
||||||
case INBOX_TYPES.INSTAGRAM:
|
|
||||||
return 'brand-instagram';
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 'chat';
|
return 'chat';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('#resolveActionName', () => {
|
|||||||
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).not.toEqual(
|
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).not.toEqual(
|
||||||
MACRO_ACTION_TYPES[0].label
|
MACRO_ACTION_TYPES[0].label
|
||||||
);
|
);
|
||||||
expect(resolveActionName('change_priority')).toEqual('CHANGE_PRIORITY'); // Translated
|
expect(resolveActionName('change_priority')).toEqual('Change Priority');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -126,44 +126,6 @@
|
|||||||
"ATLEAST_ONE_CONDITION_REQUIRED": "At least one condition is required",
|
"ATLEAST_ONE_CONDITION_REQUIRED": "At least one condition is required",
|
||||||
"ATLEAST_ONE_ACTION_REQUIRED": "At least one action is required"
|
"ATLEAST_ONE_ACTION_REQUIRED": "At least one action is required"
|
||||||
},
|
},
|
||||||
"NONE_OPTION": "None",
|
"NONE_OPTION": "None"
|
||||||
"EVENTS": {
|
|
||||||
"CONVERSATION_CREATED": "Conversation Created",
|
|
||||||
"CONVERSATION_UPDATED": "Conversation Updated",
|
|
||||||
"MESSAGE_CREATED": "Message Created",
|
|
||||||
"CONVERSATION_OPENED": "Conversation Opened"
|
|
||||||
},
|
|
||||||
"ACTIONS": {
|
|
||||||
"ASSIGN_AGENT": "Assign to Agent",
|
|
||||||
"ASSIGN_TEAM": "Assign a Team",
|
|
||||||
"ADD_LABEL": "Add a Label",
|
|
||||||
"REMOVE_LABEL": "Remove a Label",
|
|
||||||
"SEND_EMAIL_TO_TEAM": "Send an Email to Team",
|
|
||||||
"SEND_EMAIL_TRANSCRIPT": "Send an Email Transcript",
|
|
||||||
"MUTE_CONVERSATION": "Mute Conversation",
|
|
||||||
"SNOOZE_CONVERSATION": "Snooze Conversation",
|
|
||||||
"RESOLVE_CONVERSATION": "Resolve Conversation",
|
|
||||||
"SEND_WEBHOOK_EVENT": "Send Webhook Event",
|
|
||||||
"SEND_ATTACHMENT": "Send Attachment",
|
|
||||||
"SEND_MESSAGE": "Send a Message",
|
|
||||||
"CHANGE_PRIORITY": "Change Priority",
|
|
||||||
"ADD_SLA": "Add SLA"
|
|
||||||
},
|
|
||||||
"ATTRIBUTES": {
|
|
||||||
"MESSAGE_TYPE": "Message Type",
|
|
||||||
"MESSAGE_CONTAINS": "Message Contains",
|
|
||||||
"EMAIL": "Email",
|
|
||||||
"INBOX": "Inbox",
|
|
||||||
"CONVERSATION_LANGUAGE": "Conversation Language",
|
|
||||||
"PHONE_NUMBER": "Phone Number",
|
|
||||||
"STATUS": "Status",
|
|
||||||
"BROWSER_LANGUAGE": "Browser Language",
|
|
||||||
"MAIL_SUBJECT": "Email Subject",
|
|
||||||
"COUNTRY_NAME": "Country",
|
|
||||||
"REFERER_LINK": "Referrer Link",
|
|
||||||
"ASSIGNEE_NAME": "Assignee",
|
|
||||||
"TEAM_NAME": "Team",
|
|
||||||
"PRIORITY": "Priority"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
{
|
{
|
||||||
"GENERAL_SETTINGS": {
|
"GENERAL_SETTINGS": {
|
||||||
"LIMIT_MESSAGES": {
|
|
||||||
"CONVERSATION": "You have exceeded the conversation limit. Hacker plan allows only 500 conversations.",
|
|
||||||
"INBOXES": "You have exceeded the inbox limit. Hacker plan only supports website live-chat. Additional inboxes like email, WhatsApp etc. require a paid plan.",
|
|
||||||
"AGENTS": "You have exceeded the agent limit. Hacker plan allows only 2 agents.",
|
|
||||||
"NON_ADMIN": "Please contact your administrator to upgrade the plan and continue using all features."
|
|
||||||
},
|
|
||||||
"TITLE": "Account settings",
|
"TITLE": "Account settings",
|
||||||
"SUBMIT": "Update settings",
|
"SUBMIT": "Update settings",
|
||||||
"BACK": "Back",
|
"BACK": "Back",
|
||||||
@@ -14,26 +8,6 @@
|
|||||||
"ERROR": "Could not update settings, try again!",
|
"ERROR": "Could not update settings, try again!",
|
||||||
"SUCCESS": "Successfully updated account settings"
|
"SUCCESS": "Successfully updated account settings"
|
||||||
},
|
},
|
||||||
"ACCOUNT_DELETE_SECTION": {
|
|
||||||
"TITLE": "Delete your Account",
|
|
||||||
"NOTE": "Once you delete your account, all your data will be deleted.",
|
|
||||||
"BUTTON_TEXT": "Delete Your Account",
|
|
||||||
"CONFIRM": {
|
|
||||||
"TITLE": "Delete Account",
|
|
||||||
"MESSAGE": "Deleting your Account is irreversible. Enter your account name below to confirm you want to permanently delete it.",
|
|
||||||
"BUTTON_TEXT": "Delete",
|
|
||||||
"DISMISS": "Cancel",
|
|
||||||
"PLACE_HOLDER": "Please type {accountName} to confirm"
|
|
||||||
},
|
|
||||||
"SUCCESS": "Account marked for deletion",
|
|
||||||
"FAILURE": "Could not delete account, try again!",
|
|
||||||
"SCHEDULED_DELETION": {
|
|
||||||
"TITLE": "Account Scheduled for Deletion",
|
|
||||||
"MESSAGE_MANUAL": "This account is scheduled for deletion on {deletionDate}. This was requested by an administrator. You can cancel the deletion before this date.",
|
|
||||||
"MESSAGE_INACTIVITY": "This account is scheduled for deletion on {deletionDate} due to account inactivity. You can cancel the deletion before this date.",
|
|
||||||
"CLEAR_BUTTON": "Cancel Scheduled Deletion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"ERROR": "Please fix form errors",
|
"ERROR": "Please fix form errors",
|
||||||
"GENERAL_SECTION": {
|
"GENERAL_SECTION": {
|
||||||
@@ -77,7 +51,6 @@
|
|||||||
"UPDATE_CHATWOOT": "An update {latestChatwootVersion} for Chatwoot is available. Please update your instance.",
|
"UPDATE_CHATWOOT": "An update {latestChatwootVersion} for Chatwoot is available. Please update your instance.",
|
||||||
"LEARN_MORE": "Learn more",
|
"LEARN_MORE": "Learn more",
|
||||||
"PAYMENT_PENDING": "Your payment is pending. Please update your payment information to continue using Chatwoot",
|
"PAYMENT_PENDING": "Your payment is pending. Please update your payment information to continue using Chatwoot",
|
||||||
"UPGRADE": "Upgrade to continue using Chatwoot",
|
|
||||||
"LIMITS_UPGRADE": "Your account has exceeded the usage limits, please upgrade your plan to continue using Chatwoot",
|
"LIMITS_UPGRADE": "Your account has exceeded the usage limits, please upgrade your plan to continue using Chatwoot",
|
||||||
"OPEN_BILLING": "Open billing"
|
"OPEN_BILLING": "Open billing"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -43,14 +43,7 @@
|
|||||||
"INBOX_NAME": "Inbox Name",
|
"INBOX_NAME": "Inbox Name",
|
||||||
"ADD_NAME": "Add a name for your inbox",
|
"ADD_NAME": "Add a name for your inbox",
|
||||||
"PICK_NAME": "Pick a Name for your Inbox",
|
"PICK_NAME": "Pick a Name for your Inbox",
|
||||||
"PICK_A_VALUE": "Pick a value",
|
"PICK_A_VALUE": "Pick a value"
|
||||||
"CREATE_INBOX": "Create Inbox"
|
|
||||||
},
|
|
||||||
"INSTAGRAM": {
|
|
||||||
"CONTINUE_WITH_INSTAGRAM": "Continue with Instagram",
|
|
||||||
"HELP": "To add your Instagram profile as a channel, you need to authenticate your Instagram Profile by clicking on 'Continue with Instagram' ",
|
|
||||||
"ERROR_MESSAGE": "There was an error connecting to Instagram, please try again",
|
|
||||||
"ERROR_AUTH": "There was an error connecting to Instagram, please try again"
|
|
||||||
},
|
},
|
||||||
"TWITTER": {
|
"TWITTER": {
|
||||||
"HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' ",
|
"HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' ",
|
||||||
@@ -760,8 +753,7 @@
|
|||||||
"EMAIL": "Email",
|
"EMAIL": "Email",
|
||||||
"TELEGRAM": "Telegram",
|
"TELEGRAM": "Telegram",
|
||||||
"LINE": "Line",
|
"LINE": "Line",
|
||||||
"API": "API Channel",
|
"API": "API Channel"
|
||||||
"INSTAGRAM": "Instagram"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,22 +83,6 @@
|
|||||||
"ACTION_PARAMETERS_REQUIRED": "Action parameters are required",
|
"ACTION_PARAMETERS_REQUIRED": "Action parameters are required",
|
||||||
"ATLEAST_ONE_CONDITION_REQUIRED": "At least one condition is required",
|
"ATLEAST_ONE_CONDITION_REQUIRED": "At least one condition is required",
|
||||||
"ATLEAST_ONE_ACTION_REQUIRED": "At least one action is required"
|
"ATLEAST_ONE_ACTION_REQUIRED": "At least one action is required"
|
||||||
},
|
|
||||||
"ACTIONS": {
|
|
||||||
"ASSIGN_TEAM": "Assign a Team",
|
|
||||||
"ASSIGN_AGENT": "Assign an Agent",
|
|
||||||
"ADD_LABEL": "Add a Label",
|
|
||||||
"REMOVE_LABEL": "Remove a Label",
|
|
||||||
"REMOVE_ASSIGNED_TEAM": "Remove Assigned Team",
|
|
||||||
"SEND_EMAIL_TRANSCRIPT": "Send an Email Transcript",
|
|
||||||
"MUTE_CONVERSATION": "Mute Conversation",
|
|
||||||
"SNOOZE_CONVERSATION": "Snooze Conversation",
|
|
||||||
"RESOLVE_CONVERSATION": "Resolve Conversation",
|
|
||||||
"SEND_ATTACHMENT": "Send Attachment",
|
|
||||||
"SEND_MESSAGE": "Send a Message",
|
|
||||||
"CHANGE_PRIORITY": "Change Priority",
|
|
||||||
"ADD_PRIVATE_NOTE": "Add a Private Note",
|
|
||||||
"SEND_WEBHOOK_EVENT": "Send Webhook Event"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,8 +387,7 @@
|
|||||||
"LABEL": "Company Name",
|
"LABEL": "Company Name",
|
||||||
"PLACEHOLDER": "Wayne Enterprises"
|
"PLACEHOLDER": "Wayne Enterprises"
|
||||||
},
|
},
|
||||||
"SUBMIT": "Submit",
|
"SUBMIT": "Submit"
|
||||||
"CANCEL": "Cancel"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"KEYBOARD_SHORTCUTS": {
|
"KEYBOARD_SHORTCUTS": {
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<script>
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
import SocialIcons from 'dashboard/routes/dashboard/conversation/contact/SocialIcons.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
SocialIcons,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
contact: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['edit', 'message'],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
additionalAttributes() {
|
||||||
|
return this.contact.additional_attributes || {};
|
||||||
|
},
|
||||||
|
socialProfiles() {
|
||||||
|
const {
|
||||||
|
social_profiles: socialProfiles,
|
||||||
|
screen_name: twitterScreenName,
|
||||||
|
} = this.additionalAttributes;
|
||||||
|
|
||||||
|
return { twitter: twitterScreenName, ...(socialProfiles || {}) };
|
||||||
|
},
|
||||||
|
company() {
|
||||||
|
const { company = {} } = this.contact;
|
||||||
|
return company;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onEditClick() {
|
||||||
|
this.$emit('edit');
|
||||||
|
},
|
||||||
|
onNewMessageClick() {
|
||||||
|
this.$emit('message');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="contact--intro">
|
||||||
|
<Thumbnail
|
||||||
|
:src="contact.thumbnail"
|
||||||
|
size="64px"
|
||||||
|
:username="contact.name"
|
||||||
|
:status="contact.availability_status"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="contact--details">
|
||||||
|
<h2 class="text-lg contact--name">
|
||||||
|
{{ contact.name }}
|
||||||
|
</h2>
|
||||||
|
<h3 class="text-base contact--work">
|
||||||
|
{{ contact.title }}
|
||||||
|
<i v-if="company.name" class="i-lucide-circle-minus" />
|
||||||
|
<span class="company-name">{{ company.name }}</span>
|
||||||
|
</h3>
|
||||||
|
<p v-if="additionalAttributes.description" class="contact--bio">
|
||||||
|
{{ additionalAttributes.description }}
|
||||||
|
</p>
|
||||||
|
<SocialIcons :social-profiles="socialProfiles" />
|
||||||
|
</div>
|
||||||
|
<div class="contact-actions">
|
||||||
|
<woot-button
|
||||||
|
class="new-message"
|
||||||
|
size="small expanded"
|
||||||
|
icon="ion-paper-airplane"
|
||||||
|
@click="onNewMessageClick"
|
||||||
|
>
|
||||||
|
{{ $t('CONTACT_PANEL.NEW_MESSAGE') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
|
variant="hollow"
|
||||||
|
size="small expanded"
|
||||||
|
icon="edit"
|
||||||
|
@click="onEditClick"
|
||||||
|
>
|
||||||
|
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.contact--details {
|
||||||
|
margin-top: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact--work {
|
||||||
|
color: var(--color-body);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: var(--font-size-nano);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact--name {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact--bio {
|
||||||
|
margin: var(--space-smaller) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.new-message {
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: var(--space-small);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,14 +13,12 @@ import {
|
|||||||
} from '../../../helper/AnalyticsHelper/events';
|
} from '../../../helper/AnalyticsHelper/events';
|
||||||
import MenuItem from '../../../components/widgets/conversation/contextMenu/menuItem.vue';
|
import MenuItem from '../../../components/widgets/conversation/contextMenu/menuItem.vue';
|
||||||
import { useTrack } from 'dashboard/composables';
|
import { useTrack } from 'dashboard/composables';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddCannedModal,
|
AddCannedModal,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
@@ -177,12 +175,12 @@ export default {
|
|||||||
:confirm-text="$t('CONVERSATION.CONTEXT_MENU.DELETE_CONFIRMATION.DELETE')"
|
:confirm-text="$t('CONVERSATION.CONTEXT_MENU.DELETE_CONFIRMATION.DELETE')"
|
||||||
:reject-text="$t('CONVERSATION.CONTEXT_MENU.DELETE_CONFIRMATION.CANCEL')"
|
:reject-text="$t('CONVERSATION.CONTEXT_MENU.DELETE_CONFIRMATION.CANCEL')"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="!hideButton"
|
v-if="!hideButton"
|
||||||
ghost
|
icon="more-vertical"
|
||||||
slate
|
color-scheme="secondary"
|
||||||
sm
|
variant="clear"
|
||||||
icon="i-lucide-ellipsis-vertical"
|
size="small"
|
||||||
class="invisible group-hover/context-menu:visible"
|
class="invisible group-hover/context-menu:visible"
|
||||||
@click="handleOpen"
|
@click="handleOpen"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { defineAsyncComponent, ref } from 'vue';
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
|
||||||
import NextSidebar from 'next/sidebar/Sidebar.vue';
|
import NextSidebar from 'next/sidebar/Sidebar.vue';
|
||||||
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal.vue';
|
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal.vue';
|
||||||
@@ -8,7 +8,6 @@ import AddAccountModal from 'dashboard/components/layout/sidebarComponents/AddAc
|
|||||||
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
|
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
|
||||||
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
||||||
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
||||||
import UpgradePage from 'dashboard/routes/dashboard/upgrade/UpgradePage.vue';
|
|
||||||
|
|
||||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
@@ -36,10 +35,8 @@ export default {
|
|||||||
AccountSelector,
|
AccountSelector,
|
||||||
AddLabelModal,
|
AddLabelModal,
|
||||||
NotificationPanel,
|
NotificationPanel,
|
||||||
UpgradePage,
|
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const upgradePageRef = ref(null);
|
|
||||||
const { uiSettings, updateUISettings } = useUISettings();
|
const { uiSettings, updateUISettings } = useUISettings();
|
||||||
const { accountId } = useAccount();
|
const { accountId } = useAccount();
|
||||||
|
|
||||||
@@ -47,7 +44,6 @@ export default {
|
|||||||
uiSettings,
|
uiSettings,
|
||||||
updateUISettings,
|
updateUISettings,
|
||||||
accountId,
|
accountId,
|
||||||
upgradePageRef,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -68,16 +64,6 @@ export default {
|
|||||||
currentRoute() {
|
currentRoute() {
|
||||||
return ' ';
|
return ' ';
|
||||||
},
|
},
|
||||||
showUpgradePage() {
|
|
||||||
return this.upgradePageRef?.shouldShowUpgradePage;
|
|
||||||
},
|
|
||||||
bypassUpgradePage() {
|
|
||||||
return [
|
|
||||||
'billing_settings_index',
|
|
||||||
'settings_inbox_list',
|
|
||||||
'agent_list',
|
|
||||||
].includes(this.$route.name);
|
|
||||||
},
|
|
||||||
isSidebarOpen() {
|
isSidebarOpen() {
|
||||||
const { show_secondary_sidebar: showSecondarySidebar } = this.uiSettings;
|
const { show_secondary_sidebar: showSecondarySidebar } = this.uiSettings;
|
||||||
return showSecondarySidebar;
|
return showSecondarySidebar;
|
||||||
@@ -211,25 +197,8 @@ export default {
|
|||||||
@show-add-label-popup="showAddLabelPopup"
|
@show-add-label-popup="showAddLabelPopup"
|
||||||
/>
|
/>
|
||||||
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
||||||
<UpgradePage
|
<router-view />
|
||||||
v-show="showUpgradePage"
|
<CommandBar />
|
||||||
ref="upgradePageRef"
|
|
||||||
:bypass-upgrade-page="bypassUpgradePage"
|
|
||||||
/>
|
|
||||||
<template v-if="!showUpgradePage">
|
|
||||||
<router-view />
|
|
||||||
<CommandBar />
|
|
||||||
<NotificationPanel
|
|
||||||
v-if="isNotificationPanel"
|
|
||||||
@close="closeNotificationPanel"
|
|
||||||
/>
|
|
||||||
<woot-modal
|
|
||||||
v-model:show="showAddLabelModal"
|
|
||||||
:on-close="hideAddLabelPopup"
|
|
||||||
>
|
|
||||||
<AddLabelModal @close="hideAddLabelPopup" />
|
|
||||||
</woot-modal>
|
|
||||||
</template>
|
|
||||||
<AccountSelector
|
<AccountSelector
|
||||||
:show-account-modal="showAccountModal"
|
:show-account-modal="showAccountModal"
|
||||||
@close-account-modal="toggleAccountModal"
|
@close-account-modal="toggleAccountModal"
|
||||||
@@ -244,6 +213,16 @@ export default {
|
|||||||
@close="closeKeyShortcutModal"
|
@close="closeKeyShortcutModal"
|
||||||
@clickaway="closeKeyShortcutModal"
|
@clickaway="closeKeyShortcutModal"
|
||||||
/>
|
/>
|
||||||
|
<NotificationPanel
|
||||||
|
v-if="isNotificationPanel"
|
||||||
|
@close="closeNotificationPanel"
|
||||||
|
/>
|
||||||
|
<woot-modal
|
||||||
|
v-model:show="showAddLabelModal"
|
||||||
|
:on-close="hideAddLabelPopup"
|
||||||
|
>
|
||||||
|
<AddLabelModal @close="hideAddLabelPopup" />
|
||||||
|
</woot-modal>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import ConversationCard from 'dashboard/components/widgets/conversation/ConversationCard.vue';
|
import ConversationCard from 'dashboard/components/widgets/conversation/ConversationCard.vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -47,25 +47,28 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!uiFlags.isFetching" class="">
|
<div class="contact-conversation--panel">
|
||||||
<div v-if="!previousConversations.length" class="no-label-message px-4 p-3">
|
<div v-if="!uiFlags.isFetching" class="contact-conversation__wrap">
|
||||||
<span>
|
<div
|
||||||
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
|
v-if="!previousConversations.length"
|
||||||
</span>
|
class="no-label-message px-4 p-3"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-else class="contact-conversation--list">
|
||||||
|
<ConversationCard
|
||||||
|
v-for="conversation in previousConversations"
|
||||||
|
:key="conversation.id"
|
||||||
|
:chat="conversation"
|
||||||
|
:hide-inbox-name="false"
|
||||||
|
hide-thumbnail
|
||||||
|
class="compact"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="contact-conversation--list">
|
<Spinner v-else />
|
||||||
<ConversationCard
|
|
||||||
v-for="conversation in previousConversations"
|
|
||||||
:key="conversation.id"
|
|
||||||
:chat="conversation"
|
|
||||||
:hide-inbox-name="false"
|
|
||||||
hide-thumbnail
|
|
||||||
class="compact"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex items-center justify-center py-5">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -76,7 +79,6 @@ export default {
|
|||||||
|
|
||||||
::v-deep .conversation {
|
::v-deep .conversation {
|
||||||
@apply pr-0;
|
@apply pr-0;
|
||||||
|
|
||||||
.conversation--details {
|
.conversation--details {
|
||||||
@apply pl-2;
|
@apply pl-2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,12 @@ import ConversationLabels from './labels/LabelBox.vue';
|
|||||||
import { CONVERSATION_PRIORITY } from '../../../../shared/constants/messages';
|
import { CONVERSATION_PRIORITY } from '../../../../shared/constants/messages';
|
||||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||||
import { useTrack } from 'dashboard/composables';
|
import { useTrack } from 'dashboard/composables';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ContactDetailsItem,
|
ContactDetailsItem,
|
||||||
MultiselectDropdown,
|
MultiselectDropdown,
|
||||||
ConversationLabels,
|
ConversationLabels,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
conversationId: {
|
conversationId: {
|
||||||
@@ -214,15 +212,15 @@ export default {
|
|||||||
:title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')"
|
:title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')"
|
||||||
>
|
>
|
||||||
<template #button>
|
<template #button>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="showSelfAssign"
|
v-if="showSelfAssign"
|
||||||
link
|
icon="arrow-right"
|
||||||
xs
|
variant="link"
|
||||||
icon="i-lucide-arrow-right"
|
size="small"
|
||||||
class="!gap-1"
|
|
||||||
:label="$t('CONVERSATION_SIDEBAR.SELF_ASSIGN')"
|
|
||||||
@click="onSelfAssign"
|
@click="onSelfAssign"
|
||||||
/>
|
>
|
||||||
|
{{ $t('CONVERSATION_SIDEBAR.SELF_ASSIGN') }}
|
||||||
|
</woot-button>
|
||||||
</template>
|
</template>
|
||||||
</ContactDetailsItem>
|
</ContactDetailsItem>
|
||||||
<MultiselectDropdown
|
<MultiselectDropdown
|
||||||
|
|||||||
+19
-16
@@ -6,14 +6,12 @@ import { useAgentsList } from 'dashboard/composables/useAgentsList';
|
|||||||
|
|
||||||
import ThumbnailGroup from 'dashboard/components/widgets/ThumbnailGroup.vue';
|
import ThumbnailGroup from 'dashboard/components/widgets/ThumbnailGroup.vue';
|
||||||
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
|
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Spinner,
|
Spinner,
|
||||||
ThumbnailGroup,
|
ThumbnailGroup,
|
||||||
MultiselectDropdownItems,
|
MultiselectDropdownItems,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
conversationId: {
|
conversationId: {
|
||||||
@@ -167,14 +165,13 @@ export default {
|
|||||||
{{ $t('CONVERSATION_PARTICIPANTS.NO_PARTICIPANTS_TEXT') }}
|
{{ $t('CONVERSATION_PARTICIPANTS.NO_PARTICIPANTS_TEXT') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-tooltip.left="$t('CONVERSATION_PARTICIPANTS.ADD_PARTICIPANTS')"
|
v-tooltip.left="$t('CONVERSATION_PARTICIPANTS.ADD_PARTICIPANTS')"
|
||||||
slate
|
|
||||||
ghost
|
|
||||||
sm
|
|
||||||
icon="i-lucide-settings"
|
|
||||||
class="relative -top-1"
|
|
||||||
:title="$t('CONVERSATION_PARTICIPANTS.ADD_PARTICIPANTS')"
|
:title="$t('CONVERSATION_PARTICIPANTS.ADD_PARTICIPANTS')"
|
||||||
|
icon="settings"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
@click="onOpenDropdown"
|
@click="onOpenDropdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,15 +188,15 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ $t('CONVERSATION_PARTICIPANTS.YOU_ARE_WATCHING') }}
|
{{ $t('CONVERSATION_PARTICIPANTS.YOU_ARE_WATCHING') }}
|
||||||
</p>
|
</p>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-else
|
v-else
|
||||||
link
|
icon="arrow-right"
|
||||||
xs
|
variant="link"
|
||||||
icon="i-lucide-arrow-right"
|
size="small"
|
||||||
class="!gap-1"
|
|
||||||
:label="$t('CONVERSATION_PARTICIPANTS.WATCH_CONVERSATION')"
|
|
||||||
@click="onSelfAssign"
|
@click="onSelfAssign"
|
||||||
/>
|
>
|
||||||
|
{{ $t('CONVERSATION_PARTICIPANTS.WATCH_CONVERSATION') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-on-clickaway="
|
v-on-clickaway="
|
||||||
@@ -216,7 +213,13 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ $t('CONVERSATION_PARTICIPANTS.ADD_PARTICIPANTS') }}
|
{{ $t('CONVERSATION_PARTICIPANTS.ADD_PARTICIPANTS') }}
|
||||||
</h4>
|
</h4>
|
||||||
<NextButton ghost slate xs icon="i-lucide-x" @click="onCloseDropdown" />
|
<woot-button
|
||||||
|
icon="dismiss"
|
||||||
|
size="tiny"
|
||||||
|
color-scheme="secondary"
|
||||||
|
variant="clear"
|
||||||
|
@click="onCloseDropdown"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MultiselectDropdownItems
|
<MultiselectDropdownItems
|
||||||
:options="agentsList"
|
:options="agentsList"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
|
|||||||
import Draggable from 'vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||||
import MacroItem from './MacroItem.vue';
|
import MacroItem from './MacroItem.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
conversationId: {
|
conversationId: {
|
||||||
@@ -72,13 +71,9 @@ onMounted(() => {
|
|||||||
{{ $t('MACROS.LIST.404') }}
|
{{ $t('MACROS.LIST.404') }}
|
||||||
</p>
|
</p>
|
||||||
<router-link :to="accountScopedUrl('settings/macros')">
|
<router-link :to="accountScopedUrl('settings/macros')">
|
||||||
<NextButton
|
<woot-button variant="smooth" icon="add" size="tiny" class="mt-1">
|
||||||
faded
|
{{ $t('MACROS.HEADER_BTN_TXT') }}
|
||||||
xs
|
</woot-button>
|
||||||
icon="i-lucide-plus"
|
|
||||||
class="mt-1"
|
|
||||||
:label="$t('MACROS.HEADER_BTN_TXT')"
|
|
||||||
/>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -53,11 +53,9 @@ const closeMacroPreview = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative flex items-center justify-between leading-4 rounded-md h-10 pl-3 pr-2"
|
class="relative flex items-center justify-between leading-4 rounded-md button secondary clear"
|
||||||
>
|
>
|
||||||
<span
|
<span class="overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
class="overflow-hidden whitespace-nowrap text-ellipsis font-medium text-n-slate-12"
|
|
||||||
>
|
|
||||||
{{ macro.name }}
|
{{ macro.name }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-center gap-1 justify-end">
|
<div class="flex items-center gap-1 justify-end">
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const resolvedMacro = computed(() => {
|
|||||||
class="absolute -left-[0.21875rem] top-[0.2734375rem] w-2 h-2 rounded-full bg-n-solid-1 border-2 border-solid border-n-weak dark:border-slate-600"
|
class="absolute -left-[0.21875rem] top-[0.2734375rem] w-2 h-2 rounded-full bg-n-solid-1 border-2 border-solid border-n-weak dark:border-slate-600"
|
||||||
/>
|
/>
|
||||||
<p class="mb-1 text-xs text-n-slate-11">
|
<p class="mb-1 text-xs text-n-slate-11">
|
||||||
{{ $t(`MACROS.ACTIONS.${action.actionName}`) }}
|
{{ action.actionName }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-n-slate-12 text-sm">{{ action.actionValue }}</p>
|
<p class="text-n-slate-12 text-sm">{{ action.actionValue }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,12 +9,8 @@ import { useVuelidate } from '@vuelidate/core';
|
|||||||
import countries from 'shared/constants/countries.js';
|
import countries from 'shared/constants/countries.js';
|
||||||
import { isPhoneNumberValid } from 'shared/helpers/Validators';
|
import { isPhoneNumberValid } from 'shared/helpers/Validators';
|
||||||
import parsePhoneNumber from 'libphonenumber-js';
|
import parsePhoneNumber from 'libphonenumber-js';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
contact: {
|
contact: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -405,19 +401,16 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row justify-start w-full gap-2 px-0 py-2">
|
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||||
<NextButton
|
<div class="w-full">
|
||||||
type="submit"
|
<woot-submit-button
|
||||||
:label="$t('CONTACT_FORM.FORM.SUBMIT')"
|
:loading="inProgress"
|
||||||
:is-loading="inProgress"
|
:button-text="$t('CONTACT_FORM.FORM.SUBMIT')"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<button class="button clear" @click.prevent="onCancel">
|
||||||
faded
|
{{ $t('CONTACT_FORM.FORM.CANCEL') }}
|
||||||
slate
|
</button>
|
||||||
type="reset"
|
</div>
|
||||||
:label="$t('CONTACT_FORM.FORM.CANCEL')"
|
|
||||||
@click.prevent="onCancel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
||||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
EmojiOrIcon,
|
EmojiOrIcon,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
href: {
|
href: {
|
||||||
@@ -64,13 +62,15 @@ export default {
|
|||||||
<span v-else class="text-sm text-n-slate-11">
|
<span v-else class="text-sm text-n-slate-11">
|
||||||
{{ $t('CONTACT_PANEL.NOT_AVAILABLE') }}
|
{{ $t('CONTACT_PANEL.NOT_AVAILABLE') }}
|
||||||
</span>
|
</span>
|
||||||
<NextButton
|
|
||||||
|
<woot-button
|
||||||
v-if="showCopy"
|
v-if="showCopy"
|
||||||
ghost
|
type="submit"
|
||||||
xs
|
variant="clear"
|
||||||
slate
|
size="tiny"
|
||||||
class="ltr:-ml-1 rtl:-mr-1"
|
color-scheme="secondary"
|
||||||
icon="i-lucide-clipboard"
|
icon="clipboard"
|
||||||
|
class-names="p-0"
|
||||||
@click="onCopy"
|
@click="onCopy"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
+10
-10
@@ -9,7 +9,6 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||||
import CustomAttribute from 'dashboard/components/CustomAttribute.vue';
|
import CustomAttribute from 'dashboard/components/CustomAttribute.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
attributeType: {
|
attributeType: {
|
||||||
@@ -319,16 +318,17 @@ const evenClass = [
|
|||||||
{{ emptyStateMessage }}
|
{{ emptyStateMessage }}
|
||||||
</p>
|
</p>
|
||||||
<!-- Show more and show less buttons show it if the combinedElements length is greater than 5 -->
|
<!-- Show more and show less buttons show it if the combinedElements length is greater than 5 -->
|
||||||
<div v-if="combinedElements.length > 5" class="flex items-center px-2 py-2">
|
<div v-if="combinedElements.length > 5" class="flex px-2 py-2">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
size="small"
|
||||||
xs
|
:icon="showAllAttributes ? 'chevron-up' : 'chevron-down'"
|
||||||
:icon="
|
variant="clear"
|
||||||
showAllAttributes ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'
|
color-scheme="primary"
|
||||||
"
|
class="!px-2 hover:!bg-transparent dark:hover:!bg-transparent"
|
||||||
:label="toggleButtonText"
|
|
||||||
@click="onClickToggle"
|
@click="onClickToggle"
|
||||||
/>
|
>
|
||||||
|
{{ toggleButtonText }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import SwitchLayout from './SwitchLayout.vue';
|
import SwitchLayout from './SwitchLayout.vue';
|
||||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SwitchLayout,
|
SwitchLayout,
|
||||||
@@ -38,7 +37,7 @@ export default {
|
|||||||
class="flex px-4 pb-1 justify-between items-center flex-row gap-1 pt-2.5 border-b border-transparent"
|
class="flex px-4 pb-1 justify-between items-center flex-row gap-1 pt-2.5 border-b border-transparent"
|
||||||
>
|
>
|
||||||
<woot-sidemenu-icon
|
<woot-sidemenu-icon
|
||||||
size="xs"
|
size="tiny"
|
||||||
class="relative top-0 ltr:-ml-1.5 rtl:-mr-1.5 flex-shrink-0 focus:!bg-n-solid-3 dark:!hover:bg-n-solid-2 hover:!bg-n-alpha-2"
|
class="relative top-0 ltr:-ml-1.5 rtl:-mr-1.5 flex-shrink-0 focus:!bg-n-solid-3 dark:!hover:bg-n-solid-2 hover:!bg-n-alpha-2"
|
||||||
/>
|
/>
|
||||||
<router-link
|
<router-link
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<script>
|
||||||
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
|
import { required, minLength } from '@vuelidate/validators';
|
||||||
|
import { useAlert } from 'dashboard/composables';
|
||||||
|
import { CONTACTS_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||||
|
import { useTrack } from 'dashboard/composables';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
filterType: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
customViewsQuery: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
openLastSavedItem: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['close'],
|
||||||
|
setup() {
|
||||||
|
return { v$: useVuelidate() };
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isButtonDisabled() {
|
||||||
|
return this.v$.name.$invalid;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
validations: {
|
||||||
|
name: {
|
||||||
|
required,
|
||||||
|
minLength: minLength(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onClose() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
async saveCustomViews() {
|
||||||
|
this.v$.$touch();
|
||||||
|
if (this.v$.$invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('customViews/create', {
|
||||||
|
name: this.name,
|
||||||
|
filter_type: this.filterType,
|
||||||
|
query: this.customViewsQuery,
|
||||||
|
});
|
||||||
|
this.alertMessage =
|
||||||
|
this.filterType === 0
|
||||||
|
? this.$t('FILTER.CUSTOM_VIEWS.ADD.API_FOLDERS.SUCCESS_MESSAGE')
|
||||||
|
: this.$t('FILTER.CUSTOM_VIEWS.ADD.API_SEGMENTS.SUCCESS_MESSAGE');
|
||||||
|
this.onClose();
|
||||||
|
|
||||||
|
useTrack(CONTACTS_EVENTS.SAVE_FILTER, {
|
||||||
|
type: this.filterType === 0 ? 'folder' : 'segment',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error?.message;
|
||||||
|
this.alertMessage =
|
||||||
|
errorMessage || this.filterType === 0
|
||||||
|
? errorMessage
|
||||||
|
: this.$t('FILTER.CUSTOM_VIEWS.ADD.API_SEGMENTS.ERROR_MESSAGE');
|
||||||
|
} finally {
|
||||||
|
useAlert(this.alertMessage);
|
||||||
|
}
|
||||||
|
this.openLastSavedItem();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<woot-modal v-model:show="show" :on-close="onClose">
|
||||||
|
<woot-modal-header :header-title="$t('FILTER.CUSTOM_VIEWS.ADD.TITLE')" />
|
||||||
|
<form class="w-full" @submit.prevent="saveCustomViews">
|
||||||
|
<div class="w-full">
|
||||||
|
<woot-input
|
||||||
|
v-model="name"
|
||||||
|
:label="$t('FILTER.CUSTOM_VIEWS.ADD.LABEL')"
|
||||||
|
type="text"
|
||||||
|
:error="
|
||||||
|
v$.name.$error ? $t('FILTER.CUSTOM_VIEWS.ADD.ERROR_MESSAGE') : ''
|
||||||
|
"
|
||||||
|
:class="{ error: v$.name.$error }"
|
||||||
|
:placeholder="$t('FILTER.CUSTOM_VIEWS.ADD.PLACEHOLDER')"
|
||||||
|
@blur="v$.name.$touch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex flex-row justify-end w-full gap-2 px-0 py-2">
|
||||||
|
<woot-button :disabled="isButtonDisabled">
|
||||||
|
{{ $t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button variant="clear" @click.prevent="onClose">
|
||||||
|
{{ $t('FILTER.CUSTOM_VIEWS.ADD.CANCEL_BUTTON') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</woot-modal>
|
||||||
|
</template>
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import wootConstants from 'dashboard/constants/globals';
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
helpCenterDocsURL: wootConstants.HELP_CENTER_DOCS_URL,
|
helpCenterDocsURL: wootConstants.HELP_CENTER_DOCS_URL,
|
||||||
@@ -91,15 +86,21 @@ export default {
|
|||||||
v-if="isOnChatwootCloud"
|
v-if="isOnChatwootCloud"
|
||||||
class="flex flex-row gap-3 justify-start items-center sm:justify-center"
|
class="flex flex-row gap-3 justify-start items-center sm:justify-center"
|
||||||
>
|
>
|
||||||
<NextButton
|
<woot-button
|
||||||
outline
|
size="medium"
|
||||||
:label="$t('HELP_CENTER.UPGRADE_PAGE.BUTTON.LEARN_MORE')"
|
variant="hollow"
|
||||||
|
color-scheme="primary"
|
||||||
@click="openHelpCenterDocs"
|
@click="openHelpCenterDocs"
|
||||||
/>
|
>
|
||||||
<NextButton
|
{{ $t('HELP_CENTER.UPGRADE_PAGE.BUTTON.LEARN_MORE') }}
|
||||||
:label="$t('HELP_CENTER.UPGRADE_PAGE.BUTTON.UPGRADE')"
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
|
size="medium"
|
||||||
|
color-scheme="primary"
|
||||||
@click="openBillingPage"
|
@click="openBillingPage"
|
||||||
/>
|
>
|
||||||
|
{{ $t('HELP_CENTER.UPGRADE_PAGE.BUTTON.UPGRADE') }}
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
+52
-39
@@ -3,12 +3,10 @@ import { mapGetters } from 'vuex';
|
|||||||
import NotificationPanelList from './NotificationPanelList.vue';
|
import NotificationPanelList from './NotificationPanelList.vue';
|
||||||
import { useTrack } from 'dashboard/composables';
|
import { useTrack } from 'dashboard/composables';
|
||||||
import { ACCOUNT_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
import { ACCOUNT_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NotificationPanelList,
|
NotificationPanelList,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
emits: ['close'],
|
||||||
data() {
|
data() {
|
||||||
@@ -150,27 +148,28 @@ export default {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="!noUnreadNotificationAvailable"
|
v-if="!noUnreadNotificationAvailable"
|
||||||
faded
|
color-scheme="primary"
|
||||||
xs
|
variant="smooth"
|
||||||
icon="i-lucide-list-check"
|
size="tiny"
|
||||||
:is-loading="uiFlags.isUpdating"
|
:is-loading="uiFlags.isUpdating"
|
||||||
:label="$t('NOTIFICATIONS_PAGE.MARK_ALL_DONE')"
|
|
||||||
@click="onMarkAllDoneClick"
|
@click="onMarkAllDoneClick"
|
||||||
/>
|
>
|
||||||
<NextButton
|
{{ $t('NOTIFICATIONS_PAGE.MARK_ALL_DONE') }}
|
||||||
faded
|
</woot-button>
|
||||||
xs
|
<woot-button
|
||||||
slate
|
color-scheme="secondary"
|
||||||
icon="i-lucide-settings"
|
variant="smooth"
|
||||||
|
size="tiny"
|
||||||
|
icon="settings"
|
||||||
@click="openAudioNotificationSettings"
|
@click="openAudioNotificationSettings"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
color-scheme="secondary"
|
||||||
xs
|
variant="link"
|
||||||
slate
|
size="tiny"
|
||||||
icon="i-lucide-x"
|
icon="dismiss"
|
||||||
@click="closeNotificationPanel"
|
@click="closeNotificationPanel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,18 +186,25 @@ export default {
|
|||||||
class="flex items-center justify-between px-5 py-1"
|
class="flex items-center justify-between px-5 py-1"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
size="medium"
|
||||||
slate
|
variant="clear"
|
||||||
icon="i-lucide-chevrons-left"
|
color-scheme="secondary"
|
||||||
:disabled="inFirstPage"
|
:is-disabled="inFirstPage"
|
||||||
@click="onClickFirstPage"
|
@click="onClickFirstPage"
|
||||||
/>
|
>
|
||||||
<NextButton
|
<fluent-icon icon="chevron-left" size="16" />
|
||||||
ghost
|
<fluent-icon
|
||||||
slate
|
icon="chevron-left"
|
||||||
icon="i-lucide-chevron-left"
|
size="16"
|
||||||
class="ltr:-ml-3 rtl:-mr-3"
|
class="rtl:-mr-3 ltr:-ml-3"
|
||||||
|
/>
|
||||||
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
|
color-scheme="secondary"
|
||||||
|
variant="clear"
|
||||||
|
size="medium"
|
||||||
|
icon="chevron-left"
|
||||||
:disabled="inFirstPage"
|
:disabled="inFirstPage"
|
||||||
@click="onClickPreviousPage"
|
@click="onClickPreviousPage"
|
||||||
/>
|
/>
|
||||||
@@ -207,21 +213,28 @@ export default {
|
|||||||
{{ currentPage }} - {{ lastPage }}
|
{{ currentPage }} - {{ lastPage }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
color-scheme="secondary"
|
||||||
slate
|
variant="clear"
|
||||||
icon="i-lucide-chevron-right"
|
size="medium"
|
||||||
class="ltr:-mr-3 rtl:-ml-3"
|
icon="chevron-right"
|
||||||
:disabled="inLastPage"
|
:disabled="inLastPage"
|
||||||
@click="onClickNextPage"
|
@click="onClickNextPage"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<woot-button
|
||||||
ghost
|
size="medium"
|
||||||
slate
|
variant="clear"
|
||||||
icon="i-lucide-chevrons-right"
|
color-scheme="secondary"
|
||||||
:disabled="inLastPage"
|
:disabled="inLastPage"
|
||||||
@click="onClickLastPage"
|
@click="onClickLastPage"
|
||||||
/>
|
>
|
||||||
|
<fluent-icon icon="chevron-right" size="16" />
|
||||||
|
<fluent-icon
|
||||||
|
icon="chevron-right"
|
||||||
|
size="16"
|
||||||
|
class="rtl:-mr-3 ltr:-ml-3"
|
||||||
|
/>
|
||||||
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else />
|
<div v-else />
|
||||||
|
|||||||
+6
-7
@@ -1,12 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
import { dynamicTime } from 'shared/helpers/timeHelper';
|
import { dynamicTime } from 'shared/helpers/timeHelper';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
notificationItem: {
|
notificationItem: {
|
||||||
@@ -41,10 +39,11 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<NextButton
|
<woot-button
|
||||||
link
|
size="expanded"
|
||||||
slate
|
color-scheme="secondary"
|
||||||
class="!w-full !h-auto"
|
variant="link"
|
||||||
|
class="w-full"
|
||||||
@click="onClickOpenNotification()"
|
@click="onClickOpenNotification()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -101,6 +100,6 @@ export default {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NextButton>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+8
-7
@@ -2,14 +2,12 @@
|
|||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||||
import NotificationPanelItem from './NotificationPanelItem.vue';
|
import NotificationPanelItem from './NotificationPanelItem.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NotificationPanelItem,
|
NotificationPanelItem,
|
||||||
Spinner,
|
Spinner,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
notifications: {
|
notifications: {
|
||||||
@@ -61,13 +59,16 @@ export default {
|
|||||||
v-if="showEmptyResult"
|
v-if="showEmptyResult"
|
||||||
:title="$t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.EMPTY_MESSAGE')"
|
:title="$t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.EMPTY_MESSAGE')"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<woot-button
|
||||||
v-if="!isLoading && inLastPage"
|
v-if="!isLoading && inLastPage"
|
||||||
ghost
|
size="expanded"
|
||||||
class="!w-full mt-3"
|
variant="clear"
|
||||||
:label="$t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.ALL_NOTIFICATIONS')"
|
color-scheme="primary"
|
||||||
|
class-names="mt-3"
|
||||||
@click="openNotificationPage"
|
@click="openNotificationPage"
|
||||||
/>
|
>
|
||||||
|
{{ $t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.ALL_NOTIFICATIONS') }}
|
||||||
|
</woot-button>
|
||||||
<div
|
<div
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
class="flex items-center justify-center mx-2 my-12 text-sm font-medium"
|
class="flex items-center justify-center mx-2 my-12 text-sm font-medium"
|
||||||
|
|||||||
+10
-17
@@ -4,14 +4,12 @@ import Spinner from 'shared/components/Spinner.vue';
|
|||||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||||
import { dynamicTime } from 'shared/helpers/timeHelper';
|
import { dynamicTime } from 'shared/helpers/timeHelper';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
Spinner,
|
Spinner,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
notifications: {
|
notifications: {
|
||||||
@@ -51,22 +49,17 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section
|
<section
|
||||||
class="flex-grow flex-shrink h-full px-4 py-8 overflow-hidden bg-n-background"
|
class="flex-grow flex-shrink h-full px-4 py-8 overflow-hidden bg-white dark:bg-slate-900"
|
||||||
>
|
>
|
||||||
<div class="flex w-full items-center justify-between gap-2 mb-4">
|
<woot-submit-button
|
||||||
<h6 class="text-xl font-medium text-n-slate-12">
|
v-if="notificationMetadata.unreadCount"
|
||||||
{{ $t('NOTIFICATIONS_PAGE.HEADER') }}
|
class="button nice success button--fixed-top"
|
||||||
</h6>
|
:button-text="$t('NOTIFICATIONS_PAGE.MARK_ALL_DONE')"
|
||||||
<NextButton
|
:loading="isUpdating"
|
||||||
v-if="notificationMetadata.unreadCount"
|
@click="onMarkAllDoneClick"
|
||||||
type="submit"
|
/>
|
||||||
sm
|
|
||||||
:label="$t('NOTIFICATIONS_PAGE.MARK_ALL_DONE')"
|
<table class="woot-table notifications-table">
|
||||||
:is-loading="isUpdating"
|
|
||||||
@click="onMarkAllDoneClick"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<table class="woot-table notifications-table overflow-auto">
|
|
||||||
<tbody v-show="!isLoading">
|
<tbody v-show="!isLoading">
|
||||||
<tr
|
<tr
|
||||||
v-for="notificationItem in notifications"
|
v-for="notificationItem in notifications"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const routes = [
|
|||||||
path: frontendURL('accounts/:accountId/notifications'),
|
path: frontendURL('accounts/:accountId/notifications'),
|
||||||
component: SettingsWrapper,
|
component: SettingsWrapper,
|
||||||
props: {
|
props: {
|
||||||
headerTitle: '',
|
headerTitle: 'NOTIFICATIONS_PAGE.HEADER',
|
||||||
icon: '',
|
icon: 'alert',
|
||||||
showNewButton: false,
|
showNewButton: false,
|
||||||
showSidemenuIcon: false,
|
showSidemenuIcon: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||||
import BackButton from '../../../components/widgets/BackButton.vue';
|
import BackButton from '../../../components/widgets/BackButton.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BackButton,
|
BackButton,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
@@ -72,14 +70,15 @@ export default {
|
|||||||
{{ headerTitle }}
|
{{ headerTitle }}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<!-- TODO: Remove this when we are not using this -->
|
<router-link
|
||||||
<router-link v-if="showNewButton && isAdmin" :to="buttonRoute">
|
v-if="showNewButton && isAdmin"
|
||||||
<NextButton
|
:to="buttonRoute"
|
||||||
teal
|
class="button success button--fixed-top rounded-[5px] flex gap-2"
|
||||||
icon="i-lucide-circle-plus"
|
>
|
||||||
class="button--fixed-top"
|
<fluent-icon icon="add-circle" />
|
||||||
:label="buttonText"
|
<span class="button__content">
|
||||||
/>
|
{{ buttonText }}
|
||||||
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -11,15 +11,11 @@ import semver from 'semver';
|
|||||||
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||||
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||||
import V4Button from 'dashboard/components-next/button/Button.vue';
|
import V4Button from 'dashboard/components-next/button/Button.vue';
|
||||||
import WootConfirmDeleteModal from 'dashboard/components/widgets/modal/ConfirmDeleteModal.vue';
|
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseSettingsHeader,
|
BaseSettingsHeader,
|
||||||
V4Button,
|
V4Button,
|
||||||
WootConfirmDeleteModal,
|
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { updateUISettings } = useUISettings();
|
const { updateUISettings } = useUISettings();
|
||||||
@@ -39,7 +35,6 @@ export default {
|
|||||||
features: {},
|
features: {},
|
||||||
autoResolveDuration: null,
|
autoResolveDuration: null,
|
||||||
latestChatwootVersion: null,
|
latestChatwootVersion: null,
|
||||||
showDeletePopup: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
@@ -60,7 +55,6 @@ export default {
|
|||||||
getAccount: 'accounts/getAccount',
|
getAccount: 'accounts/getAccount',
|
||||||
uiFlags: 'accounts/getUIFlags',
|
uiFlags: 'accounts/getUIFlags',
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
|
||||||
}),
|
}),
|
||||||
showAutoResolutionConfig() {
|
showAutoResolutionConfig() {
|
||||||
return this.isFeatureEnabledonAccount(
|
return this.isFeatureEnabledonAccount(
|
||||||
@@ -107,34 +101,6 @@ export default {
|
|||||||
getAccountId() {
|
getAccountId() {
|
||||||
return this.id.toString();
|
return this.id.toString();
|
||||||
},
|
},
|
||||||
confirmPlaceHolderText() {
|
|
||||||
return `${this.$t(
|
|
||||||
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.PLACE_HOLDER',
|
|
||||||
{
|
|
||||||
accountName: this.name,
|
|
||||||
}
|
|
||||||
)}`;
|
|
||||||
},
|
|
||||||
isMarkedForDeletion() {
|
|
||||||
const { custom_attributes = {} } = this.currentAccount;
|
|
||||||
return !!custom_attributes.marked_for_deletion_at;
|
|
||||||
},
|
|
||||||
markedForDeletionDate() {
|
|
||||||
const { custom_attributes = {} } = this.currentAccount;
|
|
||||||
if (!custom_attributes.marked_for_deletion_at) return null;
|
|
||||||
return new Date(custom_attributes.marked_for_deletion_at);
|
|
||||||
},
|
|
||||||
markedForDeletionReason() {
|
|
||||||
const { custom_attributes = {} } = this.currentAccount;
|
|
||||||
return custom_attributes.marked_for_deletion_reason || 'manual_deletion';
|
|
||||||
},
|
|
||||||
formattedDeletionDate() {
|
|
||||||
if (!this.markedForDeletionDate) return '';
|
|
||||||
return this.markedForDeletionDate.toLocaleString();
|
|
||||||
},
|
|
||||||
currentAccount() {
|
|
||||||
return this.getAccount(this.accountId) || {};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initializeAccount();
|
this.initializeAccount();
|
||||||
@@ -196,56 +162,6 @@ export default {
|
|||||||
rtl_view: isRTLSupported,
|
rtl_view: isRTLSupported,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Delete Function
|
|
||||||
openDeletePopup() {
|
|
||||||
this.showDeletePopup = true;
|
|
||||||
},
|
|
||||||
closeDeletePopup() {
|
|
||||||
this.showDeletePopup = false;
|
|
||||||
},
|
|
||||||
async markAccountForDeletion() {
|
|
||||||
this.closeDeletePopup();
|
|
||||||
try {
|
|
||||||
// Use the enterprise API to toggle deletion with delete action
|
|
||||||
await this.$store.dispatch('accounts/toggleDeletion', {
|
|
||||||
action_type: 'delete',
|
|
||||||
});
|
|
||||||
// Refresh account data
|
|
||||||
await this.$store.dispatch('accounts/get');
|
|
||||||
useAlert(this.$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SUCCESS'));
|
|
||||||
} catch (error) {
|
|
||||||
// Handle error message
|
|
||||||
this.handleDeletionError(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleDeletionError(error) {
|
|
||||||
const errorKey = error.response?.data?.error_key;
|
|
||||||
if (errorKey) {
|
|
||||||
useAlert(
|
|
||||||
this.$t(`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.${errorKey}`)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = error.response?.data?.message;
|
|
||||||
if (message) {
|
|
||||||
useAlert(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
useAlert(this.$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.FAILURE'));
|
|
||||||
},
|
|
||||||
async clearDeletionMark() {
|
|
||||||
try {
|
|
||||||
// Use the enterprise API to toggle deletion with undelete action
|
|
||||||
await this.$store.dispatch('accounts/toggleDeletion', {
|
|
||||||
action_type: 'undelete',
|
|
||||||
});
|
|
||||||
// Refresh account data
|
|
||||||
await this.$store.dispatch('accounts/get');
|
|
||||||
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
|
|
||||||
} catch (error) {
|
|
||||||
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.ERROR'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -259,7 +175,7 @@ export default {
|
|||||||
</V4Button>
|
</V4Button>
|
||||||
</template>
|
</template>
|
||||||
</BaseSettingsHeader>
|
</BaseSettingsHeader>
|
||||||
<div class="flex-grow flex-shrink min-w-0 mt-3 overflow-auto">
|
<div class="flex-grow flex-shrink min-w-0 overflow-auto mt-3">
|
||||||
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
|
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
|
||||||
<div
|
<div
|
||||||
class="flex flex-row border-b border-slate-25 dark:border-slate-800"
|
class="flex flex-row border-b border-slate-25 dark:border-slate-800"
|
||||||
@@ -363,73 +279,6 @@ export default {
|
|||||||
<woot-code :script="getAccountId" />
|
<woot-code :script="getAccountId" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!uiFlags.isFetchingItem && isOnChatwootCloud">
|
|
||||||
<div
|
|
||||||
class="flex flex-row pt-4 mt-2 border-t border-slate-25 dark:border-slate-800 text-black-900 dark:text-slate-300"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
|
|
||||||
>
|
|
||||||
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
|
||||||
{{ $t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.TITLE') }}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{{ $t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.NOTE') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
|
||||||
<div v-if="isMarkedForDeletion">
|
|
||||||
<div
|
|
||||||
class="p-4 flex-grow-0 flex-shrink-0 flex-[50%] bg-red-50 dark:bg-red-900 rounded"
|
|
||||||
>
|
|
||||||
<p class="mb-4">
|
|
||||||
{{
|
|
||||||
$t(
|
|
||||||
`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.MESSAGE_${markedForDeletionReason === 'manual_deletion' ? 'MANUAL' : 'INACTIVITY'}`,
|
|
||||||
{
|
|
||||||
deletionDate: formattedDeletionDate,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<NextButton
|
|
||||||
:label="
|
|
||||||
$t(
|
|
||||||
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.CLEAR_BUTTON'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
color="ruby"
|
|
||||||
:is-loading="uiFlags.isUpdating"
|
|
||||||
@click="clearDeletionMark"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!isMarkedForDeletion">
|
|
||||||
<NextButton
|
|
||||||
:label="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.BUTTON_TEXT')"
|
|
||||||
color="ruby"
|
|
||||||
@click="openDeletePopup()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<WootConfirmDeleteModal
|
|
||||||
v-if="showDeletePopup"
|
|
||||||
v-model:show="showDeletePopup"
|
|
||||||
:title="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.TITLE')"
|
|
||||||
:message="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.MESSAGE')"
|
|
||||||
:confirm-text="
|
|
||||||
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.BUTTON_TEXT')
|
|
||||||
"
|
|
||||||
:reject-text="
|
|
||||||
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.DISMISS')
|
|
||||||
"
|
|
||||||
:confirm-value="name"
|
|
||||||
:confirm-place-holder-text="confirmPlaceHolderText"
|
|
||||||
@on-confirm="markAccountForDeletion"
|
|
||||||
@on-close="closeDeletePopup"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 text-sm text-center">
|
<div class="p-4 text-sm text-center">
|
||||||
<div>{{ `v${globalConfig.appVersion}` }}</div>
|
<div>{{ `v${globalConfig.appVersion}` }}</div>
|
||||||
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
|
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
|
||||||
|
|||||||
+5
-34
@@ -82,6 +82,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
||||||
|
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
||||||
automationMutated: false,
|
automationMutated: false,
|
||||||
show: true,
|
show: true,
|
||||||
showDeleteConfirmationModal: false,
|
showDeleteConfirmationModal: false,
|
||||||
@@ -95,12 +96,6 @@ export default {
|
|||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
}),
|
}),
|
||||||
automationRuleEvents() {
|
|
||||||
return AUTOMATION_RULE_EVENTS.map(event => ({
|
|
||||||
...event,
|
|
||||||
value: this.$t(`AUTOMATION.EVENTS.${event.value}`),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
hasAutomationMutated() {
|
hasAutomationMutated() {
|
||||||
if (
|
if (
|
||||||
this.automation.conditions[0].values ||
|
this.automation.conditions[0].values ||
|
||||||
@@ -110,14 +105,10 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
automationActionTypes() {
|
automationActionTypes() {
|
||||||
const actionTypes = this.isFeatureEnabled('sla')
|
const isSLAEnabled = this.isFeatureEnabled('sla');
|
||||||
|
return isSLAEnabled
|
||||||
? AUTOMATION_ACTION_TYPES
|
? AUTOMATION_ACTION_TYPES
|
||||||
: AUTOMATION_ACTION_TYPES.filter(({ key }) => key !== 'add_sla');
|
: AUTOMATION_ACTION_TYPES.filter(action => action.key !== 'add_sla');
|
||||||
|
|
||||||
return actionTypes.map(action => ({
|
|
||||||
...action,
|
|
||||||
label: this.$t(`AUTOMATION.ACTIONS.${action.label}`),
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -146,26 +137,6 @@ export default {
|
|||||||
this.$emit('saveAutomation', automation, this.mode);
|
this.$emit('saveAutomation', automation, this.mode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getTranslatedAttributes(type, event) {
|
|
||||||
return getAttributes(type, event).map(attribute => {
|
|
||||||
// Skip translation
|
|
||||||
// 1. If customAttributeType key is present then its rendering attributes from API
|
|
||||||
// 2. If contact_custom_attribute or conversation_custom_attribute is present then its rendering section title
|
|
||||||
const skipTranslation =
|
|
||||||
attribute.customAttributeType ||
|
|
||||||
[
|
|
||||||
'contact_custom_attribute',
|
|
||||||
'conversation_custom_attribute',
|
|
||||||
].includes(attribute.key);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...attribute,
|
|
||||||
name: skipTranslation
|
|
||||||
? attribute.name
|
|
||||||
: this.$t(`AUTOMATION.ATTRIBUTES.${attribute.name}`),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -233,7 +204,7 @@ export default {
|
|||||||
:key="i"
|
:key="i"
|
||||||
v-model="automation.conditions[i]"
|
v-model="automation.conditions[i]"
|
||||||
:filter-attributes="
|
:filter-attributes="
|
||||||
getTranslatedAttributes(automationTypes, automation.event_name)
|
getAttributes(automationTypes, automation.event_name)
|
||||||
"
|
"
|
||||||
:input-type="
|
:input-type="
|
||||||
getInputType(
|
getInputType(
|
||||||
|
|||||||
+5
-34
@@ -70,6 +70,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
automationRuleEvent: AUTOMATION_RULE_EVENTS[0].key,
|
||||||
|
automationRuleEvents: AUTOMATION_RULE_EVENTS,
|
||||||
automationMutated: false,
|
automationMutated: false,
|
||||||
show: true,
|
show: true,
|
||||||
showDeleteConfirmationModal: false,
|
showDeleteConfirmationModal: false,
|
||||||
@@ -83,12 +84,6 @@ export default {
|
|||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
}),
|
}),
|
||||||
automationRuleEvents() {
|
|
||||||
return AUTOMATION_RULE_EVENTS.map(event => ({
|
|
||||||
...event,
|
|
||||||
value: this.$t(`AUTOMATION.EVENTS.${event.value}`),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
hasAutomationMutated() {
|
hasAutomationMutated() {
|
||||||
if (
|
if (
|
||||||
this.automation.conditions[0].values ||
|
this.automation.conditions[0].values ||
|
||||||
@@ -98,14 +93,10 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
automationActionTypes() {
|
automationActionTypes() {
|
||||||
const actionTypes = this.isFeatureEnabled('sla')
|
const isSLAEnabled = this.isFeatureEnabled('sla');
|
||||||
|
return isSLAEnabled
|
||||||
? AUTOMATION_ACTION_TYPES
|
? AUTOMATION_ACTION_TYPES
|
||||||
: AUTOMATION_ACTION_TYPES.filter(({ key }) => key !== 'add_sla');
|
: AUTOMATION_ACTION_TYPES.filter(action => action.key !== 'add_sla');
|
||||||
|
|
||||||
return actionTypes.map(action => ({
|
|
||||||
...action,
|
|
||||||
label: this.$t(`AUTOMATION.ACTIONS.${action.label}`),
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -136,26 +127,6 @@ export default {
|
|||||||
this.$emit('saveAutomation', automation, this.mode);
|
this.$emit('saveAutomation', automation, this.mode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getTranslatedAttributes(type, event) {
|
|
||||||
return getAttributes(type, event).map(attribute => {
|
|
||||||
// Skip translation
|
|
||||||
// 1. If customAttributeType key is present then its rendering attributes from API
|
|
||||||
// 2. If contact_custom_attribute or conversation_custom_attribute is present then its rendering section title
|
|
||||||
const skipTranslation =
|
|
||||||
attribute.customAttributeType ||
|
|
||||||
[
|
|
||||||
'contact_custom_attribute',
|
|
||||||
'conversation_custom_attribute',
|
|
||||||
].includes(attribute.key);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...attribute,
|
|
||||||
name: skipTranslation
|
|
||||||
? attribute.name
|
|
||||||
: this.$t(`AUTOMATION.ATTRIBUTES.${attribute.name}`),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -216,7 +187,7 @@ export default {
|
|||||||
:key="i"
|
:key="i"
|
||||||
v-model="automation.conditions[i]"
|
v-model="automation.conditions[i]"
|
||||||
:filter-attributes="
|
:filter-attributes="
|
||||||
getTranslatedAttributes(automationTypes, automation.event_name)
|
getAttributes(automationTypes, automation.event_name)
|
||||||
"
|
"
|
||||||
:input-type="
|
:input-type="
|
||||||
getInputType(
|
getInputType(
|
||||||
|
|||||||
@@ -10,37 +10,43 @@ export const AUTOMATIONS = {
|
|||||||
conditions: [
|
conditions: [
|
||||||
{
|
{
|
||||||
key: 'message_type',
|
key: 'message_type',
|
||||||
name: 'MESSAGE_TYPE',
|
name: 'Message Type',
|
||||||
|
attributeI18nKey: 'MESSAGE_TYPE',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'content',
|
key: 'content',
|
||||||
name: 'MESSAGE_CONTAINS',
|
name: 'Message Content',
|
||||||
|
attributeI18nKey: 'MESSAGE_CONTAINS',
|
||||||
inputType: 'comma_separated_plain_text',
|
inputType: 'comma_separated_plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
name: 'EMAIL',
|
name: 'Email',
|
||||||
|
attributeI18nKey: 'EMAIL',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'inbox_id',
|
key: 'inbox_id',
|
||||||
name: 'INBOX',
|
name: 'Inbox',
|
||||||
|
attributeI18nKey: 'INBOX',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'conversation_language',
|
key: 'conversation_language',
|
||||||
name: 'CONVERSATION_LANGUAGE',
|
name: 'Conversation Language',
|
||||||
|
attributeI18nKey: 'CONVERSATION_LANGUAGE',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'phone_number',
|
key: 'phone_number',
|
||||||
name: 'PHONE_NUMBER',
|
name: 'Phone Number',
|
||||||
|
attributeI18nKey: 'PHONE_NUMBER',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_6,
|
filterOperators: OPERATOR_TYPES_6,
|
||||||
},
|
},
|
||||||
@@ -48,52 +54,64 @@ export const AUTOMATIONS = {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign to agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_team',
|
key: 'assign_team',
|
||||||
name: 'ASSIGN_TEAM',
|
name: 'Assign a team',
|
||||||
|
attributeI18nKey: 'ASSIGN_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'add_label',
|
key: 'add_label',
|
||||||
name: 'ADD_LABEL',
|
name: 'Add a label',
|
||||||
|
attributeI18nKey: 'ADD_LABEL',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'remove_label',
|
key: 'remove_label',
|
||||||
name: 'REMOVE_LABEL',
|
name: 'Remove a label',
|
||||||
|
attributeI18nKey: 'REMOVE_LABEL',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_to_team',
|
key: 'send_email_to_team',
|
||||||
name: 'SEND_EMAIL_TO_TEAM',
|
name: 'Send an email to team',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_message',
|
key: 'send_message',
|
||||||
name: 'SEND_MESSAGE',
|
name: 'Send a message',
|
||||||
|
attributeI18nKey: 'SEND_MESSAGE',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_transcript',
|
key: 'send_email_transcript',
|
||||||
name: 'SEND_EMAIL_TRANSCRIPT',
|
name: 'Send an email transcript',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mute_conversation',
|
key: 'mute_conversation',
|
||||||
name: 'MUTE_CONVERSATION',
|
name: 'Mute conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'snooze_conversation',
|
key: 'snooze_conversation',
|
||||||
name: 'SNOOZE_CONVERSATION',
|
name: 'Snooze conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: 'resolve_conversation',
|
key: 'resolve_conversation',
|
||||||
name: 'RESOLVE_CONVERSATION',
|
name: 'Resolve conversation',
|
||||||
|
attributeI18nKey: 'RESOLVE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_webhook_event',
|
key: 'send_webhook_event',
|
||||||
name: 'SEND_WEBHOOK_EVENT',
|
name: 'Send Webhook Event',
|
||||||
|
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_attachment',
|
key: 'send_attachment',
|
||||||
name: 'SEND_ATTACHMENT',
|
name: 'Send Attachment',
|
||||||
|
attributeI18nKey: 'SEND_ATTACHMENT',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -101,61 +119,71 @@ export const AUTOMATIONS = {
|
|||||||
conditions: [
|
conditions: [
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
name: 'STATUS',
|
name: 'Status',
|
||||||
|
attributeI18nKey: 'STATUS',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'browser_language',
|
key: 'browser_language',
|
||||||
name: 'BROWSER_LANGUAGE',
|
name: 'Browser Language',
|
||||||
|
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mail_subject',
|
key: 'mail_subject',
|
||||||
name: 'MAIL_SUBJECT',
|
name: 'Email Subject',
|
||||||
|
attributeI18nKey: 'MAIL_SUBJECT',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'country_code',
|
key: 'country_code',
|
||||||
name: 'COUNTRY_NAME',
|
name: 'Country',
|
||||||
|
attributeI18nKey: 'COUNTRY_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'phone_number',
|
key: 'phone_number',
|
||||||
name: 'PHONE_NUMBER',
|
name: 'Phone Number',
|
||||||
|
attributeI18nKey: 'PHONE_NUMBER',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_6,
|
filterOperators: OPERATOR_TYPES_6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'referer',
|
key: 'referer',
|
||||||
name: 'REFERER_LINK',
|
name: 'Referrer Link',
|
||||||
|
attributeI18nKey: 'REFERER_LINK',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
name: 'EMAIL',
|
name: 'Email',
|
||||||
|
attributeI18nKey: 'EMAIL',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'inbox_id',
|
key: 'inbox_id',
|
||||||
name: 'INBOX',
|
name: 'Inbox',
|
||||||
|
attributeI18nKey: 'INBOX',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'conversation_language',
|
key: 'conversation_language',
|
||||||
name: 'CONVERSATION_LANGUAGE',
|
name: 'Conversation Language',
|
||||||
|
attributeI18nKey: 'CONVERSATION_LANGUAGE',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'priority',
|
key: 'priority',
|
||||||
name: 'PRIORITY',
|
name: 'Priority',
|
||||||
|
attributeI18nKey: 'PRIORITY',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
@@ -163,47 +191,58 @@ export const AUTOMATIONS = {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign to agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_team',
|
key: 'assign_team',
|
||||||
name: 'ASSIGN_TEAM',
|
name: 'Assign a team',
|
||||||
|
attributeI18nKey: 'ASSIGN_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign an agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_to_team',
|
key: 'send_email_to_team',
|
||||||
name: 'SEND_EMAIL_TO_TEAM',
|
name: 'Send an email to team',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_message',
|
key: 'send_message',
|
||||||
name: 'SEND_MESSAGE',
|
name: 'Send a message',
|
||||||
|
attributeI18nKey: 'SEND_MESSAGE',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_transcript',
|
key: 'send_email_transcript',
|
||||||
name: 'SEND_EMAIL_TRANSCRIPT',
|
name: 'Send an email transcript',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mute_conversation',
|
key: 'mute_conversation',
|
||||||
name: 'MUTE_CONVERSATION',
|
name: 'Mute conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'snooze_conversation',
|
key: 'snooze_conversation',
|
||||||
name: 'SNOOZE_CONVERSATION',
|
name: 'Snooze conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'resolve_conversation',
|
key: 'resolve_conversation',
|
||||||
name: 'RESOLVE_CONVERSATION',
|
name: 'Resolve conversation',
|
||||||
|
attributeI18nKey: 'RESOLVE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_webhook_event',
|
key: 'send_webhook_event',
|
||||||
name: 'SEND_WEBHOOK_EVENT',
|
name: 'Send Webhook Event',
|
||||||
|
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_attachment',
|
key: 'send_attachment',
|
||||||
name: 'SEND_ATTACHMENT',
|
name: 'Send Attachment',
|
||||||
|
attributeI18nKey: 'SEND_ATTACHMENT',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -211,73 +250,85 @@ export const AUTOMATIONS = {
|
|||||||
conditions: [
|
conditions: [
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
name: 'STATUS',
|
name: 'Status',
|
||||||
|
attributeI18nKey: 'STATUS',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'browser_language',
|
key: 'browser_language',
|
||||||
name: 'BROWSER_LANGUAGE',
|
name: 'Browser Language',
|
||||||
|
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mail_subject',
|
key: 'mail_subject',
|
||||||
name: 'MAIL_SUBJECT',
|
name: 'Email Subject',
|
||||||
|
attributeI18nKey: 'MAIL_SUBJECT',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'country_code',
|
key: 'country_code',
|
||||||
name: 'COUNTRY_NAME',
|
name: 'Country',
|
||||||
|
attributeI18nKey: 'COUNTRY_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'referer',
|
key: 'referer',
|
||||||
name: 'REFERER_LINK',
|
name: 'Referrer Link',
|
||||||
|
attributeI18nKey: 'REFERER_LINK',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'phone_number',
|
key: 'phone_number',
|
||||||
name: 'PHONE_NUMBER',
|
name: 'Phone Number',
|
||||||
|
attributeI18nKey: 'PHONE_NUMBER',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_6,
|
filterOperators: OPERATOR_TYPES_6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assignee_id',
|
key: 'assignee_id',
|
||||||
name: 'ASSIGNEE_NAME',
|
name: 'Assignee',
|
||||||
|
attributeI18nKey: 'ASSIGNEE_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_3,
|
filterOperators: OPERATOR_TYPES_3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'team_id',
|
key: 'team_id',
|
||||||
name: 'TEAM_NAME',
|
name: 'Team',
|
||||||
|
attributeI18nKey: 'TEAM_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_3,
|
filterOperators: OPERATOR_TYPES_3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
name: 'EMAIL',
|
name: 'Email',
|
||||||
|
attributeI18nKey: 'EMAIL',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'inbox_id',
|
key: 'inbox_id',
|
||||||
name: 'INBOX',
|
name: 'Inbox',
|
||||||
|
attributeI18nKey: 'INBOX',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'conversation_language',
|
key: 'conversation_language',
|
||||||
name: 'CONVERSATION_LANGUAGE',
|
name: 'Conversation Language',
|
||||||
|
attributeI18nKey: 'CONVERSATION_LANGUAGE',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'priority',
|
key: 'priority',
|
||||||
name: 'PRIORITY',
|
name: 'Priority',
|
||||||
|
attributeI18nKey: 'PRIORITY',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
@@ -285,47 +336,58 @@ export const AUTOMATIONS = {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign to agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_team',
|
key: 'assign_team',
|
||||||
name: 'ASSIGN_TEAM',
|
name: 'Assign a team',
|
||||||
|
attributeI18nKey: 'ASSIGN_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign an agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_to_team',
|
key: 'send_email_to_team',
|
||||||
name: 'SEND_EMAIL_TO_TEAM',
|
name: 'Send an email to team',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_message',
|
key: 'send_message',
|
||||||
name: 'SEND_MESSAGE',
|
name: 'Send a message',
|
||||||
|
attributeI18nKey: 'SEND_MESSAGE',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_transcript',
|
key: 'send_email_transcript',
|
||||||
name: 'SEND_EMAIL_TRANSCRIPT',
|
name: 'Send an email transcript',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mute_conversation',
|
key: 'mute_conversation',
|
||||||
name: 'MUTE_CONVERSATION',
|
name: 'Mute conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'snooze_conversation',
|
key: 'snooze_conversation',
|
||||||
name: 'SNOOZE_CONVERSATION',
|
name: 'Snooze conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'resolve_conversation',
|
key: 'resolve_conversation',
|
||||||
name: 'RESOLVE_CONVERSATION',
|
name: 'Resolve conversation',
|
||||||
|
attributeI18nKey: 'RESOLVE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_webhook_event',
|
key: 'send_webhook_event',
|
||||||
name: 'SEND_WEBHOOK_EVENT',
|
name: 'Send Webhook Event',
|
||||||
|
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_attachment',
|
key: 'send_attachment',
|
||||||
name: 'SEND_ATTACHMENT',
|
name: 'Send Attachment',
|
||||||
|
attributeI18nKey: 'SEND_ATTACHMENT',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -333,67 +395,78 @@ export const AUTOMATIONS = {
|
|||||||
conditions: [
|
conditions: [
|
||||||
{
|
{
|
||||||
key: 'browser_language',
|
key: 'browser_language',
|
||||||
name: 'BROWSER_LANGUAGE',
|
name: 'Browser Language',
|
||||||
|
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
name: 'EMAIL',
|
name: 'Email',
|
||||||
|
attributeI18nKey: 'EMAIL',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mail_subject',
|
key: 'mail_subject',
|
||||||
name: 'MAIL_SUBJECT',
|
name: 'Email Subject',
|
||||||
|
attributeI18nKey: 'MAIL_SUBJECT',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'country_code',
|
key: 'country_code',
|
||||||
name: 'COUNTRY_NAME',
|
name: 'Country',
|
||||||
|
attributeI18nKey: 'COUNTRY_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'referer',
|
key: 'referer',
|
||||||
name: 'REFERER_LINK',
|
name: 'Referrer Link',
|
||||||
|
attributeI18nKey: 'REFERER_LINK',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assignee_id',
|
key: 'assignee_id',
|
||||||
name: 'ASSIGNEE_NAME',
|
name: 'Assignee',
|
||||||
|
attributeI18nKey: 'ASSIGNEE_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_3,
|
filterOperators: OPERATOR_TYPES_3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'phone_number',
|
key: 'phone_number',
|
||||||
name: 'PHONE_NUMBER',
|
name: 'Phone Number',
|
||||||
|
attributeI18nKey: 'PHONE_NUMBER',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_6,
|
filterOperators: OPERATOR_TYPES_6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'team_id',
|
key: 'team_id',
|
||||||
name: 'TEAM_NAME',
|
name: 'Team',
|
||||||
|
attributeI18nKey: 'TEAM_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
filterOperators: OPERATOR_TYPES_3,
|
filterOperators: OPERATOR_TYPES_3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'inbox_id',
|
key: 'inbox_id',
|
||||||
name: 'INBOX',
|
name: 'Inbox',
|
||||||
|
attributeI18nKey: 'INBOX',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'conversation_language',
|
key: 'conversation_language',
|
||||||
name: 'CONVERSATION_LANGUAGE',
|
name: 'Conversation Language',
|
||||||
|
attributeI18nKey: 'CONVERSATION_LANGUAGE',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'priority',
|
key: 'priority',
|
||||||
name: 'PRIORITY',
|
name: 'Priority',
|
||||||
|
attributeI18nKey: 'PRIORITY',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
filterOperators: OPERATOR_TYPES_1,
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
},
|
},
|
||||||
@@ -401,43 +474,53 @@ export const AUTOMATIONS = {
|
|||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign to agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_team',
|
key: 'assign_team',
|
||||||
name: 'ASSIGN_TEAM',
|
name: 'Assign a team',
|
||||||
|
attributeI18nKey: 'ASSIGN_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
name: 'ASSIGN_AGENT',
|
name: 'Assign an agent',
|
||||||
|
attributeI18nKey: 'ASSIGN_AGENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_to_team',
|
key: 'send_email_to_team',
|
||||||
name: 'SEND_EMAIL_TO_TEAM',
|
name: 'Send an email to team',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_message',
|
key: 'send_message',
|
||||||
name: 'SEND_MESSAGE',
|
name: 'Send a message',
|
||||||
|
attributeI18nKey: 'SEND_MESSAGE',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_transcript',
|
key: 'send_email_transcript',
|
||||||
name: 'SEND_EMAIL_TRANSCRIPT',
|
name: 'Send an email transcript',
|
||||||
|
attributeI18nKey: 'SEND_EMAIL_TRANSCRIPT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mute_conversation',
|
key: 'mute_conversation',
|
||||||
name: 'MUTE_CONVERSATION',
|
name: 'Mute conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'snooze_conversation',
|
key: 'snooze_conversation',
|
||||||
name: 'SNOOZE_CONVERSATION',
|
name: 'Snooze conversation',
|
||||||
|
attributeI18nKey: 'MUTE_CONVERSATION',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_webhook_event',
|
key: 'send_webhook_event',
|
||||||
name: 'SEND_WEBHOOK_EVENT',
|
name: 'Send Webhook Event',
|
||||||
|
attributeI18nKey: 'SEND_WEBHOOK_EVENT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_attachment',
|
key: 'send_attachment',
|
||||||
name: 'SEND_ATTACHMENT',
|
name: 'Send Attachment',
|
||||||
|
attributeI18nKey: 'SEND_ATTACHMENT',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -446,91 +529,91 @@ export const AUTOMATIONS = {
|
|||||||
export const AUTOMATION_RULE_EVENTS = [
|
export const AUTOMATION_RULE_EVENTS = [
|
||||||
{
|
{
|
||||||
key: 'conversation_created',
|
key: 'conversation_created',
|
||||||
value: 'CONVERSATION_CREATED',
|
value: 'Conversation Created',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'conversation_updated',
|
key: 'conversation_updated',
|
||||||
value: 'CONVERSATION_UPDATED',
|
value: 'Conversation Updated',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'message_created',
|
key: 'message_created',
|
||||||
value: 'MESSAGE_CREATED',
|
value: 'Message Created',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'conversation_opened',
|
key: 'conversation_opened',
|
||||||
value: 'CONVERSATION_OPENED',
|
value: 'Conversation Opened',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const AUTOMATION_ACTION_TYPES = [
|
export const AUTOMATION_ACTION_TYPES = [
|
||||||
{
|
{
|
||||||
key: 'assign_agent',
|
key: 'assign_agent',
|
||||||
label: 'ASSIGN_AGENT',
|
label: 'Assign to agent',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'assign_team',
|
key: 'assign_team',
|
||||||
label: 'ASSIGN_TEAM',
|
label: 'Assign a team',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'add_label',
|
key: 'add_label',
|
||||||
label: 'ADD_LABEL',
|
label: 'Add a label',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'remove_label',
|
key: 'remove_label',
|
||||||
label: 'REMOVE_LABEL',
|
label: 'Remove a label',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_to_team',
|
key: 'send_email_to_team',
|
||||||
label: 'SEND_EMAIL_TO_TEAM',
|
label: 'Send an email to team',
|
||||||
inputType: 'team_message',
|
inputType: 'team_message',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_email_transcript',
|
key: 'send_email_transcript',
|
||||||
label: 'SEND_EMAIL_TRANSCRIPT',
|
label: 'Send an email transcript',
|
||||||
inputType: 'email',
|
inputType: 'email',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'mute_conversation',
|
key: 'mute_conversation',
|
||||||
label: 'MUTE_CONVERSATION',
|
label: 'Mute conversation',
|
||||||
inputType: null,
|
inputType: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'snooze_conversation',
|
key: 'snooze_conversation',
|
||||||
label: 'SNOOZE_CONVERSATION',
|
label: 'Snooze conversation',
|
||||||
inputType: null,
|
inputType: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'resolve_conversation',
|
key: 'resolve_conversation',
|
||||||
label: 'RESOLVE_CONVERSATION',
|
label: 'Resolve conversation',
|
||||||
inputType: null,
|
inputType: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_webhook_event',
|
key: 'send_webhook_event',
|
||||||
label: 'SEND_WEBHOOK_EVENT',
|
label: 'Send Webhook Event',
|
||||||
inputType: 'url',
|
inputType: 'url',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_attachment',
|
key: 'send_attachment',
|
||||||
label: 'SEND_ATTACHMENT',
|
label: 'Send Attachment',
|
||||||
inputType: 'attachment',
|
inputType: 'attachment',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'send_message',
|
key: 'send_message',
|
||||||
label: 'SEND_MESSAGE',
|
label: 'Send a message',
|
||||||
inputType: 'textarea',
|
inputType: 'textarea',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'change_priority',
|
key: 'change_priority',
|
||||||
label: 'CHANGE_PRIORITY',
|
label: 'Change Priority',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'add_sla',
|
key: 'add_sla',
|
||||||
label: 'ADD_SLA',
|
label: 'Add SLA',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import Sms from './channels/Sms.vue';
|
|||||||
import Whatsapp from './channels/Whatsapp.vue';
|
import Whatsapp from './channels/Whatsapp.vue';
|
||||||
import Line from './channels/Line.vue';
|
import Line from './channels/Line.vue';
|
||||||
import Telegram from './channels/Telegram.vue';
|
import Telegram from './channels/Telegram.vue';
|
||||||
import Instagram from './channels/Instagram.vue';
|
|
||||||
|
|
||||||
const channelViewList = {
|
const channelViewList = {
|
||||||
facebook: Facebook,
|
facebook: Facebook,
|
||||||
@@ -21,7 +20,6 @@ const channelViewList = {
|
|||||||
whatsapp: Whatsapp,
|
whatsapp: Whatsapp,
|
||||||
line: Line,
|
line: Line,
|
||||||
telegram: Telegram,
|
telegram: Telegram,
|
||||||
instagram: Instagram,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import SettingIntroBanner from 'dashboard/components/widgets/SettingIntroBanner.
|
|||||||
import SettingsSection from '../../../../components/SettingsSection.vue';
|
import SettingsSection from '../../../../components/SettingsSection.vue';
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import FacebookReauthorize from './facebook/Reauthorize.vue';
|
import FacebookReauthorize from './facebook/Reauthorize.vue';
|
||||||
import InstagramReauthorize from './channels/instagram/Reauthorize.vue';
|
|
||||||
import MicrosoftReauthorize from './channels/microsoft/Reauthorize.vue';
|
import MicrosoftReauthorize from './channels/microsoft/Reauthorize.vue';
|
||||||
import GoogleReauthorize from './channels/google/Reauthorize.vue';
|
import GoogleReauthorize from './channels/google/Reauthorize.vue';
|
||||||
import PreChatFormSettings from './PreChatForm/Settings.vue';
|
import PreChatFormSettings from './PreChatForm/Settings.vue';
|
||||||
@@ -37,7 +36,6 @@ export default {
|
|||||||
MicrosoftReauthorize,
|
MicrosoftReauthorize,
|
||||||
GoogleReauthorize,
|
GoogleReauthorize,
|
||||||
NextButton,
|
NextButton,
|
||||||
InstagramReauthorize,
|
|
||||||
},
|
},
|
||||||
mixins: [inboxMixin],
|
mixins: [inboxMixin],
|
||||||
setup() {
|
setup() {
|
||||||
@@ -204,9 +202,6 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
instagramUnauthorized() {
|
|
||||||
return this.isAInstagramChannel && this.inbox.reauthorization_required;
|
|
||||||
},
|
|
||||||
microsoftUnauthorized() {
|
microsoftUnauthorized() {
|
||||||
return this.isAMicrosoftInbox && this.inbox.reauthorization_required;
|
return this.isAMicrosoftInbox && this.inbox.reauthorization_required;
|
||||||
},
|
},
|
||||||
@@ -388,11 +383,10 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</woot-tabs>
|
</woot-tabs>
|
||||||
</SettingIntroBanner>
|
</SettingIntroBanner>
|
||||||
<section class="w-full max-w-6xl mx-auto">
|
<section class="max-w-6xl mx-auto w-full">
|
||||||
<MicrosoftReauthorize v-if="microsoftUnauthorized" :inbox="inbox" />
|
<MicrosoftReauthorize v-if="microsoftUnauthorized" :inbox="inbox" />
|
||||||
<FacebookReauthorize v-if="facebookUnauthorized" :inbox="inbox" />
|
<FacebookReauthorize v-if="facebookUnauthorized" :inbox="inbox" />
|
||||||
<GoogleReauthorize v-if="googleUnauthorized" :inbox="inbox" />
|
<GoogleReauthorize v-if="googleUnauthorized" :inbox="inbox" />
|
||||||
<InstagramReauthorize v-if="instagramUnauthorized" :inbox="inbox" />
|
|
||||||
<div v-if="selectedTabKey === 'inbox_settings'" class="mx-8">
|
<div v-if="selectedTabKey === 'inbox_settings'" class="mx-8">
|
||||||
<SettingsSection
|
<SettingsSection
|
||||||
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_TITLE')"
|
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_UPDATE_TITLE')"
|
||||||
|
|||||||
+3
-8
@@ -4,14 +4,10 @@ import { useVuelidate } from '@vuelidate/core';
|
|||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import { required } from '@vuelidate/validators';
|
import { required } from '@vuelidate/validators';
|
||||||
import router from '../../../../index';
|
import router from '../../../../index';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
|
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
NextButton,
|
|
||||||
},
|
|
||||||
setup() {
|
setup() {
|
||||||
return { v$: useVuelidate() };
|
return { v$: useVuelidate() };
|
||||||
},
|
},
|
||||||
@@ -119,10 +115,9 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<NextButton
|
<woot-submit-button
|
||||||
type="submit"
|
:loading="uiFlags.isCreating"
|
||||||
:label="$t('INBOX_MGMT.ADD.WHATSAPP.SUBMIT_BUTTON')"
|
:button-text="$t('INBOX_MGMT.ADD.WHATSAPP.SUBMIT_BUTTON')"
|
||||||
:is-loading="uiFlags.isCreating"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import ChannelApi from '../../../../../api/channels';
|
|||||||
import PageHeader from '../../SettingsSubPageHeader.vue';
|
import PageHeader from '../../SettingsSubPageHeader.vue';
|
||||||
import router from '../../../../index';
|
import router from '../../../../index';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
import { loadScript } from 'dashboard/helper/DOMHelpers';
|
import { loadScript } from 'dashboard/helper/DOMHelpers';
|
||||||
import * as Sentry from '@sentry/vue';
|
import * as Sentry from '@sentry/vue';
|
||||||
@@ -20,7 +19,6 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LoadingState,
|
LoadingState,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
NextButton,
|
|
||||||
},
|
},
|
||||||
mixins: [globalConfigMixin],
|
mixins: [globalConfigMixin],
|
||||||
setup() {
|
setup() {
|
||||||
@@ -209,7 +207,7 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="w-full h-full col-span-6 p-6 overflow-auto border border-b-0 rounded-t-lg border-n-weak bg-n-solid-1"
|
class="border border-n-weak bg-n-solid-1 rounded-t-lg border-b-0 h-full w-full p-6 col-span-6 overflow-auto"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!hasLoginStarted"
|
v-if="!hasLoginStarted"
|
||||||
@@ -242,7 +240,7 @@ export default {
|
|||||||
<LoadingState v-else-if="showLoader" :message="emptyStateMessage" />
|
<LoadingState v-else-if="showLoader" :message="emptyStateMessage" />
|
||||||
<form
|
<form
|
||||||
v-else
|
v-else
|
||||||
class="flex flex-col flex-wrap mx-0"
|
class="flex flex-wrap flex-col mx-0"
|
||||||
@submit.prevent="createChannel()"
|
@submit.prevent="createChannel()"
|
||||||
>
|
>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@@ -293,7 +291,7 @@ export default {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full text-right">
|
<div class="w-full text-right">
|
||||||
<NextButton :label="$t('INBOX_MGMT.ADD.FB.CREATE_INBOX')" />
|
<input type="submit" value="Create Inbox" class="button" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user