Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8359dacd49 | ||
|
|
b71a580573 | ||
|
|
6a73953003 | ||
|
|
336af1ac9a | ||
|
|
e4de366b8d | ||
|
|
373c3dcd6c | ||
|
|
4b12dd89b0 | ||
|
|
44edafeb9a | ||
|
|
c19cfeaa81 | ||
|
|
fa118ad18c | ||
|
|
249bfe44bb | ||
|
|
826d9ec5a7 | ||
|
|
d8b53f5d2f | ||
|
|
08ced452e1 | ||
|
|
24fe3805d8 | ||
|
|
845b0c0a68 | ||
|
|
dadb311f3a | ||
|
|
8ce7539c5e | ||
|
|
5ee980465f | ||
|
|
25d5368ce8 | ||
|
|
fd633e1613 | ||
|
|
12a64f1b10 | ||
|
|
218fc46881 | ||
|
|
ffc2d98fb8 | ||
|
|
f7f04f788e | ||
|
|
a88d155dd7 | ||
|
|
eb8cb5adcc | ||
|
|
b6ddc3aa1a | ||
|
|
6699d074cf |
+7
-3
@@ -6,7 +6,7 @@ module.exports = {
|
||||
'plugin:storybook/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
parser: '@babel/eslint-parser',
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
@@ -24,13 +24,16 @@ module.exports = {
|
||||
'jsx-a11y/label-has-for': 'off',
|
||||
'jsx-a11y/anchor-is-valid': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'vue/html-indent': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/max-attributes-per-line': [
|
||||
'error',
|
||||
{
|
||||
singleline: 20,
|
||||
singleline: {
|
||||
max: 20,
|
||||
},
|
||||
multiline: {
|
||||
max: 1,
|
||||
allowFirstLine: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -47,6 +50,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/component-definition-name-casing': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'import/extensions': ['off'],
|
||||
'no-console': 'error',
|
||||
|
||||
@@ -58,6 +58,7 @@ Metrics/BlockLength:
|
||||
Metrics/ModuleLength:
|
||||
Exclude:
|
||||
- lib/seeders/message_seeder.rb
|
||||
- spec/support/slack_stubs.rb
|
||||
Rails/ApplicationController:
|
||||
Exclude:
|
||||
- 'app/controllers/api/v1/widget/messages_controller.rb'
|
||||
|
||||
@@ -159,7 +159,7 @@ gem 'lograge', '~> 0.13.0', require: false
|
||||
# worked with microsoft refresh token
|
||||
gem 'omniauth-oauth2'
|
||||
|
||||
gem 'audited', '~> 5.3'
|
||||
gem 'audited', '~> 5.4', '>= 5.4.0'
|
||||
|
||||
# need for google auth
|
||||
gem 'omniauth'
|
||||
|
||||
+3
-3
@@ -126,8 +126,8 @@ GEM
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.2)
|
||||
attr_extras (7.1.0)
|
||||
audited (5.3.3)
|
||||
activerecord (>= 5.0, < 7.1)
|
||||
audited (5.4.0)
|
||||
activerecord (>= 5.0, < 7.2)
|
||||
request_store (~> 1.2)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.760.0)
|
||||
@@ -839,7 +839,7 @@ DEPENDENCIES
|
||||
administrate-field-belongs_to_search
|
||||
annotate
|
||||
attr_extras
|
||||
audited (~> 5.3)
|
||||
audited (~> 5.4, >= 5.4.0)
|
||||
aws-sdk-s3
|
||||
azure-storage-blob!
|
||||
barnes
|
||||
|
||||
@@ -24,14 +24,6 @@ select {
|
||||
font-size: $base-font-size;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
display: block;
|
||||
font-family: $base-font-family;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
[type="color"],
|
||||
[type="date"],
|
||||
[type="datetime-local"],
|
||||
@@ -51,6 +43,7 @@ textarea {
|
||||
background-color: $white;
|
||||
border: $base-border;
|
||||
border-radius: $base-border-radius;
|
||||
font-family: $base-font-family;
|
||||
padding: 0.5em;
|
||||
transition: border-color $base-duration $base-timing;
|
||||
width: 100%;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
float: left;
|
||||
margin-bottom: $base-spacing;
|
||||
margin-top: 0.25em;
|
||||
text-align: right;
|
||||
width: calc(15% - 1rem);
|
||||
text-align: left;
|
||||
width: calc(16% - 1rem);
|
||||
}
|
||||
|
||||
.preserve-whitespace {
|
||||
@@ -17,7 +17,7 @@
|
||||
float: left;
|
||||
margin-bottom: $base-spacing;
|
||||
margin-left: 2rem;
|
||||
width: calc(85% - 1rem);
|
||||
width: calc(84% - 1rem);
|
||||
}
|
||||
|
||||
.attribute--nested {
|
||||
|
||||
@@ -10,7 +10,7 @@ input[type="submit"],
|
||||
color: $white;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: $font-size-default;
|
||||
font-size: $font-size-small;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-weight: $font-weight-medium;
|
||||
line-height: 1;
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
.main-content {
|
||||
font-size: $font-size-default;
|
||||
left: 23rem;
|
||||
left: 21rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.main-content__body {
|
||||
font-size: $font-size-small;
|
||||
padding: $space-two;
|
||||
|
||||
table {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content__header {
|
||||
@@ -20,7 +25,7 @@
|
||||
}
|
||||
|
||||
.main-content__page-title {
|
||||
font-size: $font-size-large;
|
||||
font-size: $font-size-medium;
|
||||
font-weight: $font-weight-medium;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
.logo-brand {
|
||||
margin-bottom: $space-normal;
|
||||
padding: $space-normal $space-smaller $space-small;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
|
||||
img {
|
||||
margin-bottom: $space-smaller;
|
||||
max-height: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation {
|
||||
@@ -19,12 +24,13 @@
|
||||
padding: $space-normal;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 23rem;
|
||||
width: 21rem;
|
||||
z-index: 1023;
|
||||
|
||||
li {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: $font-size-small;
|
||||
|
||||
a {
|
||||
color: $color-gray;
|
||||
@@ -35,6 +41,10 @@
|
||||
min-width: $space-medium;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: $space-slab;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation__link {
|
||||
@@ -43,7 +53,7 @@
|
||||
display: block;
|
||||
line-height: 1;
|
||||
margin-bottom: $space-smaller;
|
||||
padding: $space-one;
|
||||
padding: $space-small;
|
||||
|
||||
&:hover {
|
||||
color: $blue;
|
||||
|
||||
@@ -4,7 +4,7 @@ $base-font-family: PlusJakarta, Inter, -apple-system, BlinkMacSystemFont, "Segoe
|
||||
sans-serif !default;
|
||||
$heading-font-family: $base-font-family !default;
|
||||
|
||||
$base-font-size: 16px !default;
|
||||
$base-font-size: 14px !default;
|
||||
|
||||
$base-line-height: 1.5 !default;
|
||||
$heading-line-height: 1.2 !default;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
class Api::V1::Accounts::ContactInboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :ensure_inbox
|
||||
|
||||
def filter
|
||||
contact_inbox = @inbox.contact_inboxes.where(inbox_id: permitted_params[:inbox_id], source_id: permitted_params[:source_id])
|
||||
return head :not_found if contact_inbox.empty?
|
||||
|
||||
@contact = contact_inbox.first.contact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_inbox
|
||||
@inbox = Current.account.inboxes.find(permitted_params[:inbox_id])
|
||||
authorize @inbox, :show?
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:inbox_id, :source_id)
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,15 @@
|
||||
class Api::V1::Integrations::WebhooksController < ApplicationController
|
||||
def create
|
||||
builder = Integrations::Slack::IncomingMessageBuilder.new(params)
|
||||
builder = Integrations::Slack::IncomingMessageBuilder.new(permitted_params)
|
||||
response = builder.perform
|
||||
render json: response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO: This is a temporary solution to permit all params for slack unfurling job.
|
||||
# We should only permit the params that we use. Handle all the params based on events and send it to the respective services.
|
||||
def permitted_params
|
||||
params.permit!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,11 +9,14 @@ module RequestExceptionHandler
|
||||
|
||||
def handle_with_exception
|
||||
yield
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
log_handled_error(e)
|
||||
render_not_found_error('Resource could not be found')
|
||||
rescue Pundit::NotAuthorizedError
|
||||
rescue Pundit::NotAuthorizedError => e
|
||||
log_handled_error(e)
|
||||
render_unauthorized('You are not authorized to do this action')
|
||||
rescue ActionController::ParameterMissing => e
|
||||
log_handled_error(e)
|
||||
render_could_not_create_error(e.message)
|
||||
ensure
|
||||
# to address the thread variable leak issues in Puma/Thin webserver
|
||||
@@ -41,6 +44,7 @@ module RequestExceptionHandler
|
||||
end
|
||||
|
||||
def render_record_invalid(exception)
|
||||
log_handled_error(exception)
|
||||
render json: {
|
||||
message: exception.record.errors.full_messages.join(', '),
|
||||
attributes: exception.record.errors.attribute_names
|
||||
@@ -48,6 +52,11 @@ module RequestExceptionHandler
|
||||
end
|
||||
|
||||
def render_error_response(exception)
|
||||
log_handled_error(exception)
|
||||
render json: exception.to_hash, status: exception.http_status
|
||||
end
|
||||
|
||||
def log_handled_error(exception)
|
||||
logger.info("Handled error: #{exception.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,7 +33,7 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
|
||||
end
|
||||
|
||||
def create_conversation
|
||||
::Conversation.create!(conversation_params)
|
||||
ConversationBuilder.new(params: conversation_params, contact_inbox: @contact_inbox).perform
|
||||
end
|
||||
|
||||
def trigger_typing_event(event)
|
||||
@@ -41,11 +41,6 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @contact_inbox.contact.account_id,
|
||||
inbox_id: @contact_inbox.inbox_id,
|
||||
contact_id: @contact_inbox.contact_id,
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
}
|
||||
params.permit(custom_attributes: {})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Public::Api::V1::Portals::BaseController < PublicController
|
||||
before_action :show_plain_layout
|
||||
before_action :set_color_scheme
|
||||
around_action :set_locale
|
||||
after_action :allow_iframe_requests
|
||||
|
||||
@@ -9,6 +10,14 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
@is_plain_layout_enabled = params[:show_plain_layout] == 'true'
|
||||
end
|
||||
|
||||
def set_color_scheme
|
||||
@theme = if %w[dark light].include?(params[:theme])
|
||||
params[:theme]
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def set_locale(&)
|
||||
switch_locale_with_portal(&) if params[:locale].present?
|
||||
switch_locale_with_article(&) if params[:article_slug].present?
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
module PortalHelper
|
||||
def generate_portal_bg_color(portal_color, theme)
|
||||
base_color = theme == 'dark' ? 'black' : 'white'
|
||||
"color-mix(in srgb, #{portal_color} 10%, #{base_color})"
|
||||
end
|
||||
|
||||
def generate_portal_bg(portal_color, theme)
|
||||
bg_image = theme == 'dark' ? 'grid_dark.svg' : 'grid.svg'
|
||||
"background: url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}"
|
||||
end
|
||||
end
|
||||
@@ -111,10 +111,8 @@ export default {
|
||||
this.$store.dispatch('setActiveAccount', {
|
||||
accountId: this.currentAccountId,
|
||||
});
|
||||
const {
|
||||
locale,
|
||||
latest_chatwoot_version: latestChatwootVersion,
|
||||
} = this.getAccount(this.currentAccountId);
|
||||
const { locale, latest_chatwoot_version: latestChatwootVersion } =
|
||||
this.getAccount(this.currentAccountId);
|
||||
const { pubsub_token: pubsubToken } = this.currentUser || {};
|
||||
this.setLocale(locale);
|
||||
this.updateRTLDirectionView(locale);
|
||||
|
||||
@@ -15,9 +15,8 @@ class ApiClient {
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get accountIdFromRoute() {
|
||||
const isInsideAccountScopedURLs = window.location.pathname.includes(
|
||||
'/app/accounts'
|
||||
);
|
||||
const isInsideAccountScopedURLs =
|
||||
window.location.pathname.includes('/app/accounts');
|
||||
|
||||
if (isInsideAccountScopedURLs) {
|
||||
return window.location.pathname.split('/')[3];
|
||||
|
||||
@@ -53,6 +53,7 @@ class ContactAPI extends ApiClient {
|
||||
return axios.get(requestURL);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
filter(page = 1, sortAttr = 'name', queryPayload) {
|
||||
let requestURL = `${this.url}/filter?${buildContactParams(page, sortAttr)}`;
|
||||
return axios.post(requestURL, queryPayload);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import accountAPI from '../account';
|
||||
import ApiClient from '../../ApiClient';
|
||||
import describeWithAPIMock from '../../specs/apiSpecHelper';
|
||||
|
||||
describe('#enterpriseAccountAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -13,17 +12,33 @@ describe('#enterpriseAccountAPI', () => {
|
||||
expect(accountAPI).toHaveProperty('checkout');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#checkout', () => {
|
||||
accountAPI.checkout();
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/enterprise/api/v1/checkout'
|
||||
);
|
||||
});
|
||||
|
||||
it('#subscription', () => {
|
||||
accountAPI.subscription();
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/enterprise/api/v1/subscription'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ class ReportsAPI extends ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
getSummary(since, until, type = 'account', id, groupBy, businessHours) {
|
||||
return axios.get(`${this.url}/summary`, {
|
||||
params: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import accountAPI from '../account';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#accountAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -13,12 +12,28 @@ describe('#accountAPI', () => {
|
||||
expect(accountAPI).toHaveProperty('createAccount');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#createAccount', () => {
|
||||
accountAPI.createAccount({
|
||||
name: 'Chatwoot',
|
||||
});
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith('/api/v1/accounts', {
|
||||
expect(axiosMock.post).toHaveBeenCalledWith('/api/v1/accounts', {
|
||||
name: 'Chatwoot',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import accountActionsAPI from '../accountActions';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#ContactsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -8,10 +7,26 @@ describe('#ContactsAPI', () => {
|
||||
expect(accountActionsAPI).toHaveProperty('merge');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#merge', () => {
|
||||
accountActionsAPI.merge(1, 2);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/actions/contact_merge',
|
||||
{
|
||||
base_contact_id: 1,
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
function apiSpecHelper() {
|
||||
beforeEach(() => {
|
||||
this.originalAxios = window.axios;
|
||||
this.axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
window.axios = this.axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = this.originalAxios;
|
||||
});
|
||||
}
|
||||
// https://stackoverflow.com/a/59344023/3901856
|
||||
const sharedWrapper = describe('sharedWrapper', () => {});
|
||||
export default function describeWithAPIMock(skillName, testFn) {
|
||||
return describe(skillName, function configureContext() {
|
||||
function Context() {}
|
||||
Context.prototype = sharedWrapper.ctx;
|
||||
this.ctx = new Context();
|
||||
apiSpecHelper.call(this);
|
||||
testFn.call(this, this);
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import articlesAPI from '../helpCenter/articles';
|
||||
import ApiClient from 'dashboard/api/helpCenter/portals';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#PortalAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -12,7 +11,23 @@ describe('#PortalAPI', () => {
|
||||
expect(articlesAPI).toHaveProperty('delete');
|
||||
expect(articlesAPI).toHaveProperty('getArticles');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getArticles', () => {
|
||||
articlesAPI.getArticles({
|
||||
pageNumber: 1,
|
||||
@@ -21,30 +36,62 @@ describe('#PortalAPI', () => {
|
||||
status: 'published',
|
||||
author_id: '1',
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/portals/room-rental/articles?page=1&locale=en-US&status=published&author_id=1'
|
||||
);
|
||||
});
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getArticle', () => {
|
||||
articlesAPI.getArticle({
|
||||
id: 1,
|
||||
portalSlug: 'room-rental',
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/portals/room-rental/articles/1'
|
||||
);
|
||||
});
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#updateArticle', () => {
|
||||
articlesAPI.updateArticle({
|
||||
articleId: 1,
|
||||
portalSlug: 'room-rental',
|
||||
articleObj: { title: 'Update shipping address' },
|
||||
});
|
||||
expect(context.axiosMock.patch).toHaveBeenCalledWith(
|
||||
expect(axiosMock.patch).toHaveBeenCalledWith(
|
||||
'/api/v1/portals/room-rental/articles/1',
|
||||
{
|
||||
title: 'Update shipping address',
|
||||
@@ -52,13 +99,29 @@ describe('#PortalAPI', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#deleteArticle', () => {
|
||||
articlesAPI.deleteArticle({
|
||||
articleId: 1,
|
||||
portalSlug: 'room-rental',
|
||||
});
|
||||
expect(context.axiosMock.delete).toHaveBeenCalledWith(
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/portals/room-rental/articles/1'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
import assignableAgentsAPI from '../assignableAgents';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#AssignableAgentsAPI', () => {
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getAssignableAgents', () => {
|
||||
assignableAgentsAPI.get([1]);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/assignable_agents',
|
||||
{
|
||||
params: {
|
||||
inbox_ids: [1],
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/assignable_agents', {
|
||||
params: {
|
||||
inbox_ids: [1],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import fbChannel from '../../channel/fbChannel';
|
||||
import ApiClient from '../../ApiClient';
|
||||
import describeWithAPIMock from '../apiSpecHelper';
|
||||
|
||||
describe('#FBChannel', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -11,10 +10,26 @@ describe('#FBChannel', () => {
|
||||
expect(fbChannel).toHaveProperty('update');
|
||||
expect(fbChannel).toHaveProperty('delete');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#create', () => {
|
||||
fbChannel.create({ omniauthToken: 'ASFM131CSF@#@$', appId: 'chatwoot' });
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/callbacks/register_facebook_page',
|
||||
{
|
||||
omniauthToken: 'ASFM131CSF@#@$',
|
||||
@@ -27,7 +42,7 @@ describe('#FBChannel', () => {
|
||||
omniauthToken: 'ASFM131CSF@#@$',
|
||||
inboxId: 1,
|
||||
});
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/callbacks/reauthorize_page',
|
||||
{
|
||||
omniauth_token: 'ASFM131CSF@#@$',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import contactAPI, { buildContactParams } from '../contacts';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#ContactsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -15,56 +14,67 @@ describe('#ContactsAPI', () => {
|
||||
expect(contactAPI).toHaveProperty('destroyAvatar');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#get', () => {
|
||||
contactAPI.get(1, 'name', 'customer-support');
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts?include_contact_inboxes=false&page=1&sort=name&labels[]=customer-support'
|
||||
);
|
||||
});
|
||||
|
||||
it('#getConversations', () => {
|
||||
contactAPI.getConversations(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/1/conversations'
|
||||
);
|
||||
});
|
||||
|
||||
it('#getContactableInboxes', () => {
|
||||
contactAPI.getContactableInboxes(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/1/contactable_inboxes'
|
||||
);
|
||||
});
|
||||
|
||||
it('#getContactLabels', () => {
|
||||
contactAPI.getContactLabels(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/1/labels'
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/contacts/1/labels');
|
||||
});
|
||||
|
||||
it('#updateContactLabels', () => {
|
||||
const labels = ['support-query'];
|
||||
contactAPI.updateContactLabels(1, labels);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/1/labels',
|
||||
{
|
||||
labels,
|
||||
}
|
||||
);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith('/api/v1/contacts/1/labels', {
|
||||
labels,
|
||||
});
|
||||
});
|
||||
|
||||
it('#search', () => {
|
||||
contactAPI.search('leads', 1, 'date', 'customer-support');
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/search?include_contact_inboxes=false&page=1&sort=date&q=leads&labels[]=customer-support'
|
||||
);
|
||||
});
|
||||
|
||||
it('#destroyCustomAttributes', () => {
|
||||
contactAPI.destroyCustomAttributes(1, ['cloudCustomer']);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/1/destroy_custom_attributes',
|
||||
{
|
||||
custom_attributes: ['cloudCustomer'],
|
||||
@@ -75,7 +85,7 @@ describe('#ContactsAPI', () => {
|
||||
it('#importContacts', () => {
|
||||
const file = 'file';
|
||||
contactAPI.importContacts(file);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/import',
|
||||
expect.any(FormData),
|
||||
{
|
||||
@@ -96,7 +106,7 @@ describe('#ContactsAPI', () => {
|
||||
],
|
||||
};
|
||||
contactAPI.filter(1, 'name', queryPayload);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/filter?include_contact_inboxes=false&page=1&sort=name',
|
||||
queryPayload
|
||||
);
|
||||
@@ -104,7 +114,7 @@ describe('#ContactsAPI', () => {
|
||||
|
||||
it('#destroyAvatar', () => {
|
||||
contactAPI.destroyAvatar(1);
|
||||
expect(context.axiosMock.delete).toHaveBeenCalledWith(
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/1/avatar'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import conversationsAPI from '../conversations';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#ConversationApi', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -14,10 +13,26 @@ describe('#ConversationApi', () => {
|
||||
expect(conversationsAPI).toHaveProperty('updateLabels');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getLabels', () => {
|
||||
conversationsAPI.getLabels(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/1/labels'
|
||||
);
|
||||
});
|
||||
@@ -25,7 +40,7 @@ describe('#ConversationApi', () => {
|
||||
it('#updateLabels', () => {
|
||||
const labels = ['support-query'];
|
||||
conversationsAPI.updateLabels(1, labels);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/1/labels',
|
||||
{
|
||||
labels,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import csatReportsAPI from '../csatReports';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#Reports API', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -9,10 +8,26 @@ describe('#Reports API', () => {
|
||||
expect(csatReportsAPI).toHaveProperty('get');
|
||||
expect(csatReportsAPI).toHaveProperty('getMetrics');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#get', () => {
|
||||
csatReportsAPI.get({ page: 1, from: 1622485800, to: 1623695400 });
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/csat_survey_responses',
|
||||
{
|
||||
params: {
|
||||
@@ -26,7 +41,7 @@ describe('#Reports API', () => {
|
||||
});
|
||||
it('#getMetrics', () => {
|
||||
csatReportsAPI.getMetrics({ from: 1622485800, to: 1623695400 });
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/csat_survey_responses/metrics',
|
||||
{
|
||||
params: { since: 1622485800, until: 1623695400 },
|
||||
@@ -39,7 +54,7 @@ describe('#Reports API', () => {
|
||||
to: 1623695400,
|
||||
user_ids: 1,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/csat_survey_responses/download',
|
||||
{
|
||||
params: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import conversationAPI from '../../inbox/conversation';
|
||||
import ApiClient from '../../ApiClient';
|
||||
import describeWithAPIMock from '../apiSpecHelper';
|
||||
|
||||
describe('#ConversationAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -22,7 +21,23 @@ describe('#ConversationAPI', () => {
|
||||
expect(conversationAPI).toHaveProperty('filter');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#get conversations', () => {
|
||||
conversationAPI.get({
|
||||
inboxId: 1,
|
||||
@@ -32,19 +47,16 @@ describe('#ConversationAPI', () => {
|
||||
labels: [],
|
||||
teamId: 1,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations',
|
||||
{
|
||||
params: {
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
status: 'open',
|
||||
assignee_type: 'me',
|
||||
page: 1,
|
||||
labels: [],
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/conversations', {
|
||||
params: {
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
status: 'open',
|
||||
assignee_type: 'me',
|
||||
page: 1,
|
||||
labels: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#search', () => {
|
||||
@@ -53,7 +65,7 @@ describe('#ConversationAPI', () => {
|
||||
page: 1,
|
||||
});
|
||||
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/search',
|
||||
{
|
||||
params: {
|
||||
@@ -66,7 +78,7 @@ describe('#ConversationAPI', () => {
|
||||
|
||||
it('#toggleStatus', () => {
|
||||
conversationAPI.toggleStatus({ conversationId: 12, status: 'online' });
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/toggle_status`,
|
||||
{
|
||||
status: 'online',
|
||||
@@ -77,7 +89,7 @@ describe('#ConversationAPI', () => {
|
||||
|
||||
it('#assignAgent', () => {
|
||||
conversationAPI.assignAgent({ conversationId: 12, agentId: 34 });
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/assignments?assignee_id=34`,
|
||||
{}
|
||||
);
|
||||
@@ -85,7 +97,7 @@ describe('#ConversationAPI', () => {
|
||||
|
||||
it('#assignTeam', () => {
|
||||
conversationAPI.assignTeam({ conversationId: 12, teamId: 1 });
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/assignments`,
|
||||
{
|
||||
team_id: 1,
|
||||
@@ -95,7 +107,7 @@ describe('#ConversationAPI', () => {
|
||||
|
||||
it('#markMessageRead', () => {
|
||||
conversationAPI.markMessageRead({ id: 12 });
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/update_last_seen`
|
||||
);
|
||||
});
|
||||
@@ -105,7 +117,7 @@ describe('#ConversationAPI', () => {
|
||||
conversationId: 12,
|
||||
status: 'typing_on',
|
||||
});
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/toggle_typing_status`,
|
||||
{
|
||||
typing_status: 'typing_on',
|
||||
@@ -115,14 +127,14 @@ describe('#ConversationAPI', () => {
|
||||
|
||||
it('#mute', () => {
|
||||
conversationAPI.mute(45);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/45/mute'
|
||||
);
|
||||
});
|
||||
|
||||
it('#unmute', () => {
|
||||
conversationAPI.unmute(45);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/45/unmute'
|
||||
);
|
||||
});
|
||||
@@ -135,18 +147,15 @@ describe('#ConversationAPI', () => {
|
||||
labels: [],
|
||||
teamId: 1,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/meta',
|
||||
{
|
||||
params: {
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
status: 'open',
|
||||
assignee_type: 'me',
|
||||
labels: [],
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/conversations/meta', {
|
||||
params: {
|
||||
inbox_id: 1,
|
||||
team_id: 1,
|
||||
status: 'open',
|
||||
assignee_type: 'me',
|
||||
labels: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#sendEmailTranscript', () => {
|
||||
@@ -154,7 +163,7 @@ describe('#ConversationAPI', () => {
|
||||
conversationId: 45,
|
||||
email: 'john@acme.inc',
|
||||
});
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/45/transcript',
|
||||
{
|
||||
email: 'john@acme.inc',
|
||||
@@ -167,7 +176,7 @@ describe('#ConversationAPI', () => {
|
||||
conversationId: 45,
|
||||
customAttributes: { order_d: '1001' },
|
||||
});
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/45/custom_attributes',
|
||||
{
|
||||
custom_attributes: { order_d: '1001' },
|
||||
@@ -202,9 +211,7 @@ describe('#ConversationAPI', () => {
|
||||
},
|
||||
};
|
||||
conversationAPI.filter(payload);
|
||||
expect(
|
||||
context.axiosMock.post
|
||||
).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/filter',
|
||||
payload.queryData,
|
||||
{ params: { page: payload.page } }
|
||||
@@ -213,7 +220,7 @@ describe('#ConversationAPI', () => {
|
||||
|
||||
it('#getAllAttachments', () => {
|
||||
conversationAPI.getAllAttachments(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/conversations/1/attachments'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import messageAPI, { buildCreatePayload } from '../../inbox/message';
|
||||
import ApiClient from '../../ApiClient';
|
||||
import describeWithAPIMock from '../apiSpecHelper';
|
||||
|
||||
describe('#ConversationAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -13,13 +12,29 @@ describe('#ConversationAPI', () => {
|
||||
expect(messageAPI).toHaveProperty('getPreviousMessages');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getPreviousMessages', () => {
|
||||
messageAPI.getPreviousMessages({
|
||||
conversationId: 12,
|
||||
before: 4573,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
`/api/v1/conversations/12/messages`,
|
||||
{
|
||||
params: {
|
||||
@@ -35,7 +50,6 @@ describe('#ConversationAPI', () => {
|
||||
message: 'test content',
|
||||
echoId: 12,
|
||||
isPrivate: true,
|
||||
|
||||
files: [new Blob(['test-content'], { type: 'application/pdf' })],
|
||||
});
|
||||
expect(formPayload).toBeInstanceOf(FormData);
|
||||
@@ -58,8 +72,10 @@ describe('#ConversationAPI', () => {
|
||||
private: false,
|
||||
echo_id: 12,
|
||||
content_attributes: { in_reply_to: 12 },
|
||||
bcc_emails: '',
|
||||
cc_emails: '',
|
||||
bcc_emails: '',
|
||||
to_emails: '',
|
||||
template_params: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import inboxesAPI from '../inboxes';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#InboxesAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -14,19 +13,32 @@ describe('#InboxesAPI', () => {
|
||||
expect(inboxesAPI).toHaveProperty('getAgentBot');
|
||||
expect(inboxesAPI).toHaveProperty('setAgentBot');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getCampaigns', () => {
|
||||
inboxesAPI.getCampaigns(2);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/inboxes/2/campaigns'
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/inboxes/2/campaigns');
|
||||
});
|
||||
|
||||
it('#deleteInboxAvatar', () => {
|
||||
inboxesAPI.deleteInboxAvatar(2);
|
||||
expect(context.axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/inboxes/2/avatar'
|
||||
);
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/inboxes/2/avatar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import integrationAPI from '../integrations';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#integrationAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -16,11 +15,27 @@ describe('#integrationAPI', () => {
|
||||
expect(integrationAPI).toHaveProperty('listAllSlackChannels');
|
||||
expect(integrationAPI).toHaveProperty('deleteHook');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#connectSlack', () => {
|
||||
const code = 'SDNFJNSDFNDSJN';
|
||||
integrationAPI.connectSlack(code);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/slack',
|
||||
{
|
||||
code,
|
||||
@@ -31,7 +46,7 @@ describe('#integrationAPI', () => {
|
||||
it('#updateSlack', () => {
|
||||
const updateObj = { referenceId: 'SDFSDGSVE' };
|
||||
integrationAPI.updateSlack(updateObj);
|
||||
expect(context.axiosMock.patch).toHaveBeenCalledWith(
|
||||
expect(axiosMock.patch).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/slack',
|
||||
{
|
||||
reference_id: updateObj.referenceId,
|
||||
@@ -41,16 +56,14 @@ describe('#integrationAPI', () => {
|
||||
|
||||
it('#listAllSlackChannels', () => {
|
||||
integrationAPI.listAllSlackChannels();
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/slack/list_all_channels'
|
||||
);
|
||||
});
|
||||
|
||||
it('#delete', () => {
|
||||
integrationAPI.delete(2);
|
||||
expect(context.axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/2'
|
||||
);
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/integrations/2');
|
||||
});
|
||||
|
||||
it('#createHook', () => {
|
||||
@@ -59,7 +72,7 @@ describe('#integrationAPI', () => {
|
||||
settings: { api_key: 'SDFSDGSVE' },
|
||||
};
|
||||
integrationAPI.createHook(hookData);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/hooks',
|
||||
hookData
|
||||
);
|
||||
@@ -67,7 +80,7 @@ describe('#integrationAPI', () => {
|
||||
|
||||
it('#deleteHook', () => {
|
||||
integrationAPI.deleteHook(2);
|
||||
expect(context.axiosMock.delete).toHaveBeenCalledWith(
|
||||
expect(axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/hooks/2'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import DyteAPIClient from '../../integrations/dyte';
|
||||
import ApiClient from '../../ApiClient';
|
||||
import describeWithAPIMock from '../apiSpecHelper';
|
||||
|
||||
describe('#accountAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -9,10 +8,26 @@ describe('#accountAPI', () => {
|
||||
expect(DyteAPIClient).toHaveProperty('addParticipantToMeeting');
|
||||
});
|
||||
|
||||
describeWithAPIMock('createAMeeting', context => {
|
||||
describe('createAMeeting', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
DyteAPIClient.createAMeeting(1);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/dyte/create_a_meeting',
|
||||
{
|
||||
conversation_id: 1,
|
||||
@@ -21,10 +36,26 @@ describe('#accountAPI', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describeWithAPIMock('addParticipantToMeeting', context => {
|
||||
describe('addParticipantToMeeting', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
DyteAPIClient.addParticipantToMeeting(1);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/dyte/add_participant_to_meeting',
|
||||
{
|
||||
message_id: 1,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import notificationsAPI from '../notifications';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#NotificationAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -11,31 +10,47 @@ describe('#NotificationAPI', () => {
|
||||
expect(notificationsAPI).toHaveProperty('read');
|
||||
expect(notificationsAPI).toHaveProperty('readAll');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#get', () => {
|
||||
notificationsAPI.get(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/notifications?page=1'
|
||||
);
|
||||
});
|
||||
|
||||
it('#getNotifications', () => {
|
||||
notificationsAPI.getNotifications(1);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/notifications/1/notifications'
|
||||
);
|
||||
});
|
||||
|
||||
it('#getUnreadCount', () => {
|
||||
notificationsAPI.getUnreadCount();
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/notifications/unread_count'
|
||||
);
|
||||
});
|
||||
|
||||
it('#read', () => {
|
||||
notificationsAPI.read(48670, 'Conversation');
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/notifications/read_all',
|
||||
{
|
||||
primary_actor_id: 'Conversation',
|
||||
@@ -46,7 +61,7 @@ describe('#NotificationAPI', () => {
|
||||
|
||||
it('#readAll', () => {
|
||||
notificationsAPI.readAll();
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/notifications/read_all'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import reportsAPI from '../reports';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#Reports API', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -18,14 +17,30 @@ describe('#Reports API', () => {
|
||||
expect(reportsAPI).toHaveProperty('getInboxReports');
|
||||
expect(reportsAPI).toHaveProperty('getTeamReports');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getAccountReports', () => {
|
||||
reportsAPI.getReports({
|
||||
metric: 'conversations_count',
|
||||
from: 1621103400,
|
||||
to: 1621621800,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith('/api/v2/reports', {
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v2/reports', {
|
||||
params: {
|
||||
metric: 'conversations_count',
|
||||
since: 1621103400,
|
||||
@@ -38,20 +53,17 @@ describe('#Reports API', () => {
|
||||
|
||||
it('#getAccountSummary', () => {
|
||||
reportsAPI.getSummary(1621103400, 1621621800);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/summary',
|
||||
{
|
||||
params: {
|
||||
business_hours: undefined,
|
||||
group_by: undefined,
|
||||
id: undefined,
|
||||
since: 1621103400,
|
||||
timezone_offset: -0,
|
||||
type: 'account',
|
||||
until: 1621621800,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v2/reports/summary', {
|
||||
params: {
|
||||
business_hours: undefined,
|
||||
group_by: undefined,
|
||||
id: undefined,
|
||||
since: 1621103400,
|
||||
timezone_offset: -0,
|
||||
type: 'account',
|
||||
until: 1621621800,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#getAgentReports', () => {
|
||||
@@ -60,60 +72,48 @@ describe('#Reports API', () => {
|
||||
to: 1621621800,
|
||||
businessHours: true,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/agents',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
business_hours: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v2/reports/agents', {
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
business_hours: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#getLabelReports', () => {
|
||||
reportsAPI.getLabelReports({ from: 1621103400, to: 1621621800 });
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/labels',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v2/reports/labels', {
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#getInboxReports', () => {
|
||||
reportsAPI.getInboxReports({ from: 1621103400, to: 1621621800 });
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/inboxes',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v2/reports/inboxes', {
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#getTeamReports', () => {
|
||||
reportsAPI.getTeamReports({ from: 1621103400, to: 1621621800 });
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/teams',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(axiosMock.get).toHaveBeenCalledWith('/api/v2/reports/teams', {
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('#getConversationMetric', () => {
|
||||
reportsAPI.getConversationMetric('account');
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/conversations',
|
||||
{
|
||||
params: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import teamsAPI from '../teams';
|
||||
import ApiClient from '../ApiClient';
|
||||
import describeWithAPIMock from './apiSpecHelper';
|
||||
|
||||
describe('#TeamsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
@@ -14,17 +13,33 @@ describe('#TeamsAPI', () => {
|
||||
expect(teamsAPI).toHaveProperty('addAgents');
|
||||
expect(teamsAPI).toHaveProperty('updateAgents');
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
describe('API calls', () => {
|
||||
const originalAxios = window.axios;
|
||||
const axiosMock = {
|
||||
post: jest.fn(() => Promise.resolve()),
|
||||
get: jest.fn(() => Promise.resolve()),
|
||||
patch: jest.fn(() => Promise.resolve()),
|
||||
delete: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.axios = axiosMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('#getAgents', () => {
|
||||
teamsAPI.getAgents({ teamId: 1 });
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v1/teams/1/team_members'
|
||||
);
|
||||
});
|
||||
|
||||
it('#addAgents', () => {
|
||||
teamsAPI.addAgents({ teamId: 1, agentsList: { user_ids: [1, 10, 21] } });
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/teams/1/team_members',
|
||||
{
|
||||
user_ids: { user_ids: [1, 10, 21] },
|
||||
@@ -38,7 +53,7 @@ describe('#TeamsAPI', () => {
|
||||
teamId: 1,
|
||||
agentsList,
|
||||
});
|
||||
expect(context.axiosMock.patch).toHaveBeenCalledWith(
|
||||
expect(axiosMock.patch).toHaveBeenCalledWith(
|
||||
'/api/v1/teams/1/team_members',
|
||||
{
|
||||
user_ids: agentsList,
|
||||
|
||||
@@ -149,15 +149,11 @@
|
||||
}
|
||||
|
||||
.multiselect-wrap--small {
|
||||
.multiselect__tags,
|
||||
.multiselect__input {
|
||||
@apply items-center flex;
|
||||
}
|
||||
|
||||
.multiselect__tags,
|
||||
.multiselect__input,
|
||||
.multiselect {
|
||||
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 rounded-[5px] text-sm h-10 min-h-[2.5rem];
|
||||
@apply bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 rounded-[5px] text-sm min-h-[2.5rem];
|
||||
}
|
||||
|
||||
.multiselect__input {
|
||||
@@ -165,11 +161,15 @@
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
@apply items-center flex m-0 text-sm max-h-[2.375rem] text-slate-800 dark:text-slate-100 bg-white dark:bg-slate-900 py-1 px-0.5;
|
||||
@apply items-center flex m-0 text-sm max-h-[2.375rem] text-slate-800 dark:text-slate-100 bg-white dark:bg-slate-900 py-3 px-0.5;
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
@apply m-0 py-1 px-0.5;
|
||||
@apply m-0 py-2 px-0.5;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
@apply py-[6px] my-[1px];
|
||||
}
|
||||
|
||||
.multiselect__select {
|
||||
@@ -181,9 +181,6 @@
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.multiselect__tags-wrap {
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect-wrap--medium {
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<div
|
||||
class="flex items-center justify-between py-0 px-4"
|
||||
:class="{
|
||||
'pb-3 border-b border-slate-75 dark:border-slate-700': hasAppliedFiltersOrActiveFolders,
|
||||
'pb-3 border-b border-slate-75 dark:border-slate-700':
|
||||
hasAppliedFiltersOrActiveFolders,
|
||||
}"
|
||||
>
|
||||
<div class="flex max-w-[85%] justify-center items-center">
|
||||
@@ -24,9 +25,7 @@
|
||||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
|
||||
>
|
||||
{{
|
||||
this.$t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`)
|
||||
}}
|
||||
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
@@ -642,10 +641,8 @@ export default {
|
||||
},
|
||||
handleKeyEvents(e) {
|
||||
if (hasPressedAltAndJKey(e)) {
|
||||
const {
|
||||
allConversations,
|
||||
activeConversationIndex,
|
||||
} = this.getKeyboardListenerParams();
|
||||
const { allConversations, activeConversationIndex } =
|
||||
this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[0].click();
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
/>
|
||||
<div class="flex flex-row justify-end gap-2 py-2 px-0 w-full">
|
||||
<woot-button variant="clear" @click.prevent="onClose">
|
||||
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
|
||||
{{ $t('CONVERSATION.CUSTOM_SNOOZE.CANCEL') }}
|
||||
</woot-button>
|
||||
<woot-button>
|
||||
{{ this.$t('CONVERSATION.CUSTOM_SNOOZE.APPLY') }}
|
||||
{{ $t('CONVERSATION.CUSTOM_SNOOZE.APPLY') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="text--container">
|
||||
<woot-button size="small" class=" button--text" @click="onCopy">
|
||||
<woot-button size="small" class="button--text" @click="onCopy">
|
||||
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
|
||||
@@ -66,9 +66,9 @@ export default {
|
||||
'right-aligned': 'right-aligned',
|
||||
};
|
||||
|
||||
return `modal-mask skip-context-menu ${modalClassNameMap[
|
||||
this.modalType
|
||||
] || ''}`;
|
||||
return `modal-mask skip-context-menu ${
|
||||
modalClassNameMap[this.modalType] || ''
|
||||
}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div
|
||||
class="ml-0 mr-0 flex pt-0 pr-4 pb-4 pl-0"
|
||||
:class="{
|
||||
'pt-4 border-b border-solid border-slate-50 dark:border-slate-700/30': showBorder,
|
||||
'pt-4 border-b border-solid border-slate-50 dark:border-slate-700/30':
|
||||
showBorder,
|
||||
}"
|
||||
>
|
||||
<div class="w-[30%] min-w-0 max-w-[30%] pr-12">
|
||||
|
||||
@@ -72,10 +72,8 @@ export default {
|
||||
const { custom_attributes: subscription } = account;
|
||||
if (!subscription) return EMPTY_SUBSCRIPTION_INFO;
|
||||
|
||||
const {
|
||||
subscription_status: status,
|
||||
subscription_ends_on: endsOn,
|
||||
} = subscription;
|
||||
const { subscription_status: status, subscription_ends_on: endsOn } =
|
||||
subscription;
|
||||
|
||||
return { status, endsOn: new Date(endsOn) };
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const semver = require('semver');
|
||||
import semver from 'semver';
|
||||
|
||||
export const hasAnUpdateAvailable = (latestVersion, currentVersion) => {
|
||||
if (!semver.valid(latestVersion)) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdResolveConversation"
|
||||
>
|
||||
{{ this.$t('CONVERSATION.HEADER.RESOLVE_ACTION') }}
|
||||
{{ $t('CONVERSATION.HEADER.RESOLVE_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-else-if="isResolved"
|
||||
@@ -21,7 +21,7 @@
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdOpenConversation"
|
||||
>
|
||||
{{ this.$t('CONVERSATION.HEADER.REOPEN_ACTION') }}
|
||||
{{ $t('CONVERSATION.HEADER.REOPEN_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-else-if="showOpenButton"
|
||||
@@ -31,7 +31,7 @@
|
||||
:is-loading="isLoading"
|
||||
@click="onCmdOpenConversation"
|
||||
>
|
||||
{{ this.$t('CONVERSATION.HEADER.OPEN_ACTION') }}
|
||||
{{ $t('CONVERSATION.HEADER.OPEN_ACTION') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="showAdditionalActions"
|
||||
@@ -57,7 +57,7 @@
|
||||
icon="snooze"
|
||||
@click="() => openSnoozeModal()"
|
||||
>
|
||||
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
|
||||
{{ $t('CONVERSATION.RESOLVE_DROPDOWN.SNOOZE_UNTIL') }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-item v-if="!isPending">
|
||||
@@ -68,7 +68,7 @@
|
||||
icon="book-clock"
|
||||
@click="() => toggleStatus(STATUS_TYPE.PENDING)"
|
||||
>
|
||||
{{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
|
||||
{{ $t('CONVERSATION.RESOLVE_DROPDOWN.MARK_PENDING') }}
|
||||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
</woot-dropdown-menu>
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
<woot-submit-button
|
||||
:disabled="
|
||||
$v.accountName.$invalid ||
|
||||
$v.accountName.$invalid ||
|
||||
uiFlags.isCreating
|
||||
$v.accountName.$invalid ||
|
||||
uiFlags.isCreating
|
||||
"
|
||||
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
|
||||
:loading="uiFlags.isCreating"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<button
|
||||
class="text-slate-600 dark:text-slate-100 w-10 h-10 my-2 flex items-center justify-center rounded-lg hover:bg-slate-25 dark:hover:bg-slate-700 dark:hover:text-slate-100 hover:text-slate-600 relative"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50': isNotificationPanelActive,
|
||||
'bg-woot-50 dark:bg-slate-800 text-woot-500 hover:bg-woot-50':
|
||||
isNotificationPanelActive,
|
||||
}"
|
||||
@click="openNotificationPanel"
|
||||
>
|
||||
|
||||
+4
-2
@@ -9,7 +9,8 @@
|
||||
class="font-medium h-7 my-1 hover:bg-slate-25 hover:text-bg-50 flex items-center px-2 rounded-md dark:hover:bg-slate-800"
|
||||
:class="{
|
||||
'bg-woot-25 dark:bg-slate-800': isActive,
|
||||
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full': shouldTruncate,
|
||||
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full':
|
||||
shouldTruncate,
|
||||
}"
|
||||
@click="navigate"
|
||||
>
|
||||
@@ -44,7 +45,8 @@
|
||||
class="text-sm text-slate-700 dark:text-slate-100"
|
||||
:class="{
|
||||
'text-woot-500 dark:text-woot-500': isActive,
|
||||
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full': shouldTruncate,
|
||||
'text-ellipsis overflow-hidden whitespace-nowrap max-w-full':
|
||||
shouldTruncate,
|
||||
}"
|
||||
>
|
||||
{{ label }}
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
:class="{
|
||||
'text-slate-300 dark:text-slate-600': isCountZero && !isActiveView,
|
||||
'text-slate-600 dark:text-slate-50': !isCountZero && !isActiveView,
|
||||
'bg-woot-75 dark:bg-woot-200 text-woot-600 dark:text-woot-600': isActiveView,
|
||||
'bg-woot-75 dark:bg-woot-200 text-woot-600 dark:text-woot-600':
|
||||
isActiveView,
|
||||
'bg-slate-50 dark:bg-slate-700': !isActiveView,
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -53,16 +53,24 @@ export default {
|
||||
const createdTimeDiff = Date.now() - this.createdAtTimestamp * 1000;
|
||||
const isBeforeAMonth = createdTimeDiff > DAY_IN_MILLI_SECONDS * 30;
|
||||
return !isBeforeAMonth
|
||||
? `Created ${this.createdAtTimeAgo}`
|
||||
: `Created at: ${this.dateFormat(this.createdAtTimestamp)}`;
|
||||
? `${this.$t('CHAT_LIST.CHAT_TIME_STAMP.CREATED.LATEST')} ${
|
||||
this.createdAtTimeAgo
|
||||
}`
|
||||
: `${this.$t(
|
||||
'CHAT_LIST.CHAT_TIME_STAMP.CREATED.OLDEST'
|
||||
)} ${this.dateFormat(this.createdAtTimestamp)}`;
|
||||
},
|
||||
lastActivity() {
|
||||
const lastActivityTimeDiff =
|
||||
Date.now() - this.lastActivityTimestamp * 1000;
|
||||
const isNotActive = lastActivityTimeDiff > DAY_IN_MILLI_SECONDS * 30;
|
||||
return !isNotActive
|
||||
? `Last activity ${this.lastActivityAtTimeAgo}`
|
||||
: `Last activity: ${this.dateFormat(this.lastActivityTimestamp)}`;
|
||||
? `${this.$t('CHAT_LIST.CHAT_TIME_STAMP.LAST_ACTIVITY.ACTIVE')} ${
|
||||
this.lastActivityAtTimeAgo
|
||||
}`
|
||||
: `${this.$t(
|
||||
'CHAT_LIST.CHAT_TIME_STAMP.LAST_ACTIVITY.NOT_ACTIVE'
|
||||
)} ${this.dateFormat(this.lastActivityTimestamp)}`;
|
||||
},
|
||||
tooltipText() {
|
||||
return `${this.createdAt}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="animation-container margin-top-1">
|
||||
<div class="ai-typing--wrap ">
|
||||
<div class="ai-typing--wrap">
|
||||
<fluent-icon icon="wand" size="14" class="ai-typing--icon" />
|
||||
<label>
|
||||
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.ASSISTANCE_MODAL.AI_WRITING') }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
@@ -255,7 +256,9 @@ export default {
|
||||
value === 'conversation_attribute' ||
|
||||
value === 'contact_attribute'
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.value.custom_attribute_type = this.customAttributeType;
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
} else this.value.custom_attribute_type = '';
|
||||
},
|
||||
immediate: true,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
suggestionsPlugin,
|
||||
triggerCharacters,
|
||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
import TagAgents from '../conversation/TagAgents.vue';
|
||||
import CannedResponse from '../conversation/CannedResponse.vue';
|
||||
@@ -47,6 +49,8 @@ import VariableList from '../conversation/VariableList.vue';
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
insertAtCursor,
|
||||
scrollCursorIntoView,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
|
||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
@@ -72,7 +76,9 @@ import { MESSAGE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||
const createState = (
|
||||
content,
|
||||
placeholder,
|
||||
// eslint-disable-next-line default-param-last
|
||||
plugins = [],
|
||||
// eslint-disable-next-line default-param-last
|
||||
methods = {},
|
||||
enabledMenuOptions
|
||||
) => {
|
||||
@@ -270,6 +276,7 @@ export default {
|
||||
const tr = this.editorView.state.tr.replaceSelectionWith(node);
|
||||
this.editorView.focus();
|
||||
this.state = this.editorView.state.apply(tr);
|
||||
this.editorView.updateState(this.state);
|
||||
this.emitOnChange();
|
||||
this.$emit('clear-selection');
|
||||
}
|
||||
@@ -295,7 +302,17 @@ export default {
|
||||
mounted() {
|
||||
this.createEditorView();
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditor(this.value);
|
||||
this.focusEditorInputField();
|
||||
|
||||
// BUS Event to insert text or markdown into the editor at the
|
||||
// current cursor position.
|
||||
// Components using this
|
||||
// 1. SearchPopover.vue
|
||||
|
||||
bus.$on(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, this.insertContentIntoEditor);
|
||||
},
|
||||
beforeDestroy() {
|
||||
bus.$off(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, this.insertContentIntoEditor);
|
||||
},
|
||||
methods: {
|
||||
reloadState(content = this.value) {
|
||||
@@ -382,6 +399,7 @@ export default {
|
||||
state: this.state,
|
||||
dispatchTransaction: tx => {
|
||||
this.state = this.state.apply(tx);
|
||||
this.editorView.updateState(this.state);
|
||||
this.emitOnChange();
|
||||
},
|
||||
handleDOMEvents: {
|
||||
@@ -438,11 +456,7 @@ export default {
|
||||
userFullName: mentionItem.name,
|
||||
});
|
||||
|
||||
const tr = this.editorView.state.tr
|
||||
.replaceWith(this.range.from, this.range.to, node)
|
||||
.insertText(` `);
|
||||
this.state = this.editorView.state.apply(tr);
|
||||
this.emitOnChange();
|
||||
this.insertNodeIntoEditor(node, this.range.from, this.range.to);
|
||||
this.$track(CONVERSATION_EVENTS.USED_MENTIONS);
|
||||
|
||||
return false;
|
||||
@@ -456,26 +470,12 @@ export default {
|
||||
return null;
|
||||
}
|
||||
|
||||
let from = this.range.from - 1;
|
||||
let node = new MessageMarkdownTransformer(messageSchema).parse(
|
||||
updatedMessage
|
||||
);
|
||||
|
||||
if (node.textContent === updatedMessage) {
|
||||
node = this.editorView.state.schema.text(updatedMessage);
|
||||
from = this.range.from;
|
||||
}
|
||||
this.insertNodeIntoEditor(node, this.range.from, this.range.to);
|
||||
|
||||
const tr = this.editorView.state.tr.replaceWith(
|
||||
from,
|
||||
this.range.to,
|
||||
node
|
||||
);
|
||||
|
||||
this.state = this.editorView.state.apply(tr);
|
||||
this.emitOnChange();
|
||||
|
||||
tr.scrollIntoView();
|
||||
this.$track(CONVERSATION_EVENTS.INSERTED_A_CANNED_RESPONSE);
|
||||
return false;
|
||||
},
|
||||
@@ -483,23 +483,14 @@ export default {
|
||||
if (!this.editorView) {
|
||||
return null;
|
||||
}
|
||||
let node = this.editorView.state.schema.text(`{{${variable}}}`);
|
||||
const from = this.range.from;
|
||||
|
||||
const tr = this.editorView.state.tr.replaceWith(
|
||||
from,
|
||||
this.range.to,
|
||||
node
|
||||
);
|
||||
const content = `{{${variable}}}`;
|
||||
let node = this.editorView.state.schema.text(content);
|
||||
const { from, to } = this.range;
|
||||
|
||||
this.state = this.editorView.state.apply(tr);
|
||||
this.emitOnChange();
|
||||
|
||||
// The `{{ }}` are added to the message, but the cursor is placed
|
||||
// and onExit of suggestionsPlugin is not called. So we need to manually hide
|
||||
this.insertNodeIntoEditor(node, from, to);
|
||||
this.showVariables = false;
|
||||
this.$track(CONVERSATION_EVENTS.INSERTED_A_VARIABLE);
|
||||
tr.scrollIntoView();
|
||||
return false;
|
||||
},
|
||||
openFileBrowser() {
|
||||
@@ -555,8 +546,6 @@ export default {
|
||||
},
|
||||
|
||||
emitOnChange() {
|
||||
this.editorView.updateState(this.state);
|
||||
|
||||
this.$emit('input', this.contentFromEditor);
|
||||
},
|
||||
|
||||
@@ -616,6 +605,19 @@ export default {
|
||||
onFocus() {
|
||||
this.$emit('focus');
|
||||
},
|
||||
insertContentIntoEditor(content, defaultFrom = 0) {
|
||||
const from = defaultFrom || this.editorView.state.selection.from || 0;
|
||||
let node = new MessageMarkdownTransformer(messageSchema).parse(content);
|
||||
|
||||
this.insertNodeIntoEditor(node, from, undefined);
|
||||
},
|
||||
insertNodeIntoEditor(node, from = 0, to = 0) {
|
||||
this.state = insertAtCursor(this.editorView, node, from, to);
|
||||
this.emitOnChange();
|
||||
this.$nextTick(() => {
|
||||
scrollCursorIntoView(this.editorView);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -709,6 +711,6 @@ export default {
|
||||
}
|
||||
|
||||
.editor-warning__message {
|
||||
@apply text-red-400 dark:text-red-400 font-normal pt-1 pb-0 px-0;
|
||||
@apply text-red-400 dark:text-red-400 text-sm font-normal pt-1 pb-0 px-0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,7 +32,9 @@ const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
|
||||
const createState = (
|
||||
content,
|
||||
placeholder,
|
||||
// eslint-disable-next-line default-param-last
|
||||
plugins = [],
|
||||
// eslint-disable-next-line default-param-last
|
||||
methods = {},
|
||||
enabledMenuOptions
|
||||
) => {
|
||||
|
||||
+1
-3
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
`status-badge status-badge__${status} rounded-full w-2.5 h-2.5 mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-flex`
|
||||
"
|
||||
:class="`status-badge status-badge__${status} rounded-full w-2.5 h-2.5 mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-flex`"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
>
|
||||
<div class="items-center flex justify-between last:mt-4">
|
||||
<span class="text-slate-800 dark:text-slate-100 text-xs font-medium">{{
|
||||
this.$t('CHAT_LIST.CHAT_SORT.STATUS')
|
||||
$t('CHAT_LIST.CHAT_SORT.STATUS')
|
||||
}}</span>
|
||||
<filter-item
|
||||
type="status"
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="items-center flex justify-between last:mt-4">
|
||||
<span class="text-slate-800 dark:text-slate-100 text-xs font-medium">{{
|
||||
this.$t('CHAT_LIST.CHAT_SORT.ORDER_BY')
|
||||
$t('CHAT_LIST.CHAT_SORT.ORDER_BY')
|
||||
}}</span>
|
||||
<filter-item
|
||||
type="sort"
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
||||
:icon="attachmentIcon"
|
||||
/>
|
||||
{{ this.$t(`${attachmentMessageContent}`) }}
|
||||
{{ $t(`${attachmentMessageContent}`) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t('CHAT_LIST.NO_CONTENT') }}
|
||||
@@ -103,7 +103,7 @@
|
||||
icon="info"
|
||||
/>
|
||||
<span>
|
||||
{{ this.$t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||
</span>
|
||||
</p>
|
||||
<div class="conversation--meta flex flex-col absolute right-4 top-4">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<woot-modal :show.sync="show" :on-close="onCancel">
|
||||
<div class="h-auto overflow-auto flex flex-col">
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div
|
||||
class="my-0 mx-4 px-1 flex max-h-[8vh] items-baseline justify-between hover:bg-slate-25 dark:hover:bg-slate-800 border border-dashed border-slate-100 dark:border-slate-700 rounded-sm overflow-auto"
|
||||
>
|
||||
<p class="w-fit !m-0">
|
||||
{{ $t('CONVERSATION.FOOTER.MESSAGE_SIGNATURE_NOT_CONFIGURED') }}
|
||||
<woot-button
|
||||
color-scheme="primary"
|
||||
variant="link"
|
||||
@click="openProfileSettings"
|
||||
>
|
||||
{{ $t('CONVERSATION.FOOTER.CLICK_HERE') }}
|
||||
</woot-button>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
openProfileSettings() {
|
||||
return this.$router.push({ name: 'profile_settings_index' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -450,16 +450,14 @@ export default {
|
||||
|
||||
// label suggestions are not part of the messages list
|
||||
// so we need to handle them separately
|
||||
let labelSuggestions = this.conversationPanel.querySelector(
|
||||
'.label-suggestion'
|
||||
);
|
||||
let labelSuggestions =
|
||||
this.conversationPanel.querySelector('.label-suggestion');
|
||||
|
||||
// if there are unread messages, scroll to the first unread message
|
||||
if (this.unreadMessageCount > 0) {
|
||||
// capturing only the unread messages
|
||||
relevantMessages = this.conversationPanel.querySelectorAll(
|
||||
'.message--unread'
|
||||
);
|
||||
relevantMessages =
|
||||
this.conversationPanel.querySelectorAll('.message--unread');
|
||||
} else if (labelSuggestions) {
|
||||
// when scrolling to the bottom, the label suggestions is below the last message
|
||||
// so we scroll there if there are no unread messages
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
}"
|
||||
class="shrink-0 rounded-sm inline-flex w-3.5 h-3.5"
|
||||
:class="{
|
||||
'bg-red-50 dark:bg-red-700 dark:bg-opacity-30 text-red-500 dark:text-red-600': isUrgent,
|
||||
'bg-slate-50 dark:bg-slate-700 text-slate-600 dark:text-slate-200': !isUrgent,
|
||||
'bg-red-50 dark:bg-red-700 dark:bg-opacity-30 text-red-500 dark:text-red-600':
|
||||
isUrgent,
|
||||
'bg-slate-50 dark:bg-slate-700 text-slate-600 dark:text-slate-200':
|
||||
!isUrgent,
|
||||
}"
|
||||
>
|
||||
<fluent-icon
|
||||
|
||||
@@ -90,17 +90,9 @@
|
||||
:remove-attachment="removeAttachment"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
<message-signature-missing-alert
|
||||
v-if="isSignatureEnabledForInbox && !isSignatureAvailable"
|
||||
class="message-signature-wrap"
|
||||
>
|
||||
<p class="message-signature">
|
||||
{{ $t('CONVERSATION.FOOTER.MESSAGE_SIGNATURE_NOT_CONFIGURED') }}
|
||||
<router-link :to="profilePath">
|
||||
{{ $t('CONVERSATION.FOOTER.CLICK_HERE') }}
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
/>
|
||||
<reply-bottom-panel
|
||||
:conversation-id="conversationId"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
@@ -154,6 +146,7 @@ import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.v
|
||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
||||
import ReplyEmailHead from './ReplyEmailHead.vue';
|
||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
|
||||
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
@@ -171,7 +164,6 @@ import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
|
||||
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
import { trimContent, debounce } from '@chatwoot/utils';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
||||
@@ -200,6 +192,7 @@ export default {
|
||||
WootAudioRecorder,
|
||||
Banner,
|
||||
WhatsappTemplates,
|
||||
MessageSignatureMissingAlert,
|
||||
},
|
||||
mixins: [
|
||||
clickaway,
|
||||
@@ -431,9 +424,8 @@ export default {
|
||||
const {
|
||||
LAYOUT_TYPES: { CONDENSED },
|
||||
} = wootConstants;
|
||||
const {
|
||||
conversation_display_type: conversationDisplayType = CONDENSED,
|
||||
} = this.uiSettings;
|
||||
const { conversation_display_type: conversationDisplayType = CONDENSED } =
|
||||
this.uiSettings;
|
||||
return conversationDisplayType !== CONDENSED;
|
||||
},
|
||||
emojiDialogClassOnExpandedLayoutAndRTLView() {
|
||||
@@ -481,9 +473,6 @@ export default {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
return isEnabled;
|
||||
},
|
||||
profilePath() {
|
||||
return frontendURL(`accounts/${this.accountId}/profile/settings`);
|
||||
},
|
||||
editorMessageKey() {
|
||||
const { editor_message_key: isEnabled } = this.uiSettings;
|
||||
return isEnabled;
|
||||
@@ -566,7 +555,7 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.getFromDraft();
|
||||
// Donot use the keyboard listener mixin here as the events here are supposed to be
|
||||
// Don't use the keyboard listener mixin here as the events here are supposed to be
|
||||
// working even if input/textarea is focussed.
|
||||
document.addEventListener('paste', this.onPaste);
|
||||
document.addEventListener('keydown', this.handleKeyEvents);
|
||||
@@ -1084,14 +1073,6 @@ export default {
|
||||
@apply py-2;
|
||||
}
|
||||
|
||||
.message-signature-wrap {
|
||||
@apply my-0 mx-4 px-1 flex max-h-[8vh] items-baseline justify-between hover:bg-slate-25 dark:hover:bg-slate-800 border border-dashed border-slate-100 dark:border-slate-700 rounded-sm overflow-auto;
|
||||
}
|
||||
|
||||
.message-signature {
|
||||
@apply w-fit m-0;
|
||||
}
|
||||
|
||||
.attachment-preview-box {
|
||||
@apply bg-transparent py-0 px-4;
|
||||
}
|
||||
@@ -1137,13 +1118,6 @@ export default {
|
||||
@apply left-1 -bottom-2;
|
||||
}
|
||||
}
|
||||
.message-signature {
|
||||
@apply mb-0;
|
||||
|
||||
::v-deep p:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
}
|
||||
|
||||
.normal-editor__canned-box {
|
||||
width: calc(100% - 2 * var(--space-normal));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<woot-modal :show.sync="show" :on-close="onClose" size="modal-big">
|
||||
<woot-modal-header
|
||||
|
||||
@@ -187,8 +187,9 @@ export default {
|
||||
return '';
|
||||
}
|
||||
const { screenName, sourceId } = this;
|
||||
return `https://twitter.com/${screenName ||
|
||||
this.inbox.name}/status/${sourceId}`;
|
||||
return `https://twitter.com/${
|
||||
screenName || this.inbox.name
|
||||
}/status/${sourceId}`;
|
||||
},
|
||||
linkToStory() {
|
||||
if (!this.storyId || !this.storySender) {
|
||||
|
||||
+2
-3
@@ -57,9 +57,8 @@ export default {
|
||||
async joinTheCall() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const {
|
||||
data: { authResponse: { authToken } = {} } = {},
|
||||
} = await DyteAPI.addParticipantToMeeting(this.messageId);
|
||||
const { data: { authResponse: { authToken } = {} } = {} } =
|
||||
await DyteAPI.addParticipantToMeeting(this.messageId);
|
||||
this.dyteAuthToken = authToken;
|
||||
} catch (err) {
|
||||
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR'));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<woot-modal
|
||||
full-width
|
||||
@@ -212,8 +213,13 @@ export default {
|
||||
return this.activeFileType === ALLOWED_FILE_TYPES.AUDIO;
|
||||
},
|
||||
senderDetails() {
|
||||
const { name, available_name: availableName, avatar_url, thumbnail, id } =
|
||||
this.activeAttachment?.sender || this.attachment?.sender;
|
||||
const {
|
||||
name,
|
||||
available_name: availableName,
|
||||
avatar_url,
|
||||
thumbnail,
|
||||
id,
|
||||
} = this.activeAttachment?.sender || this.attachment?.sender || {};
|
||||
const currentUserID = this.currentUser?.id;
|
||||
return {
|
||||
name: currentUserID === id ? 'You' : name || availableName || '',
|
||||
|
||||
@@ -34,15 +34,13 @@
|
||||
:option="labelMenuConfig"
|
||||
:sub-menu-available="!!labels.length"
|
||||
>
|
||||
<template>
|
||||
<menu-item
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:option="generateMenuLabelConfig(label, 'label')"
|
||||
variant="label"
|
||||
@click="$emit('assign-label', label)"
|
||||
/>
|
||||
</template>
|
||||
<menu-item
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:option="generateMenuLabelConfig(label, 'label')"
|
||||
variant="label"
|
||||
@click="$emit('assign-label', label)"
|
||||
/>
|
||||
</menu-item-with-submenu>
|
||||
<menu-item-with-submenu
|
||||
:option="agentMenuConfig"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
type="button"
|
||||
@click="onAvatarDelete"
|
||||
>
|
||||
{{ this.$t('INBOX_MGMT.DELETE.AVATAR_DELETE_BUTTON_TEXT') }}
|
||||
{{ $t('INBOX_MGMT.DELETE.AVATAR_DELETE_BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<label>
|
||||
|
||||
@@ -63,8 +63,9 @@
|
||||
<div v-if="filteredCountriesBySearch.length === 0">
|
||||
<span
|
||||
class="flex items-center justify-center text-sm text-slate-500 dark:text-slate-300 mt-4"
|
||||
>No results found</span
|
||||
>
|
||||
No results found
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<modal :show.sync="show" :on-close="closeModal">
|
||||
<woot-modal-header :header-title="title" :header-content="message" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<modal :show.sync="show" :on-close="onClose">
|
||||
<woot-modal-header
|
||||
|
||||
@@ -30,9 +30,7 @@
|
||||
<hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</hotkey>
|
||||
<hotkey custom-class="min-h-[28px] w-9 key">
|
||||
J
|
||||
</hotkey>
|
||||
<hotkey custom-class="min-h-[28px] w-9 key"> J </hotkey>
|
||||
<span
|
||||
class="flex items-center font-semibold text-sm text-slate-800 dark:text-slate-100"
|
||||
>
|
||||
@@ -42,9 +40,7 @@
|
||||
<hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</hotkey>
|
||||
<hotkey custom-class="w-9 key">
|
||||
K
|
||||
</hotkey>
|
||||
<hotkey custom-class="w-9 key"> K </hotkey>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,9 +55,7 @@
|
||||
<hotkey custom-class="min-h-[28px] min-w-[60px] normal-case key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</hotkey>
|
||||
<hotkey custom-class="w-9 key">
|
||||
E
|
||||
</hotkey>
|
||||
<hotkey custom-class="w-9 key"> E </hotkey>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -97,6 +97,7 @@ class DashboardAudioNotificationHelper {
|
||||
return conversationAssigneeId === this.currentUserId;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
isMessageFromCurrentConversation = message => {
|
||||
return (
|
||||
window.WOOT.$store.getters.getSelectedChat?.id === message.conversation_id
|
||||
|
||||
@@ -60,6 +60,7 @@ export const conversationListPageURL = ({
|
||||
|
||||
export const isValidURL = value => {
|
||||
/* eslint-disable no-useless-escape */
|
||||
const URL_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/gm;
|
||||
const URL_REGEX =
|
||||
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/gm;
|
||||
return URL_REGEX.test(value);
|
||||
};
|
||||
|
||||
@@ -102,6 +102,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
this.app.$store.dispatch('updateConversation', data);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onLogout = () => AuthAPI.logout();
|
||||
|
||||
onMessageCreated = data => {
|
||||
@@ -117,6 +118,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onReload = () => window.location.reload();
|
||||
|
||||
onStatusChange = data => {
|
||||
@@ -171,6 +173,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
}, 30000);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
fetchConversationStats = () => {
|
||||
bus.$emit('fetch_conversation_stats');
|
||||
bus.$emit('fetch_overview_reports');
|
||||
@@ -192,6 +195,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
this.app.$store.dispatch('notifications/addNotification', data);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onFirstReplyCreated = () => {
|
||||
bus.$emit('fetch_overview_reports');
|
||||
};
|
||||
|
||||
@@ -259,7 +259,9 @@ export const isCustomAttribute = (attrs, key) => {
|
||||
};
|
||||
|
||||
export const generateCustomAttributes = (
|
||||
// eslint-disable-next-line default-param-last
|
||||
conversationAttributes = [],
|
||||
// eslint-disable-next-line default-param-last
|
||||
contactAttributes = [],
|
||||
conversationlabel,
|
||||
contactlabel
|
||||
|
||||
@@ -70,15 +70,8 @@ const getValuesForCountries = (values, countries) => {
|
||||
|
||||
export const getValuesForFilter = (filter, params) => {
|
||||
const { attribute_key, values } = filter;
|
||||
const {
|
||||
languages,
|
||||
countries,
|
||||
agents,
|
||||
inboxes,
|
||||
teams,
|
||||
campaigns,
|
||||
labels,
|
||||
} = params;
|
||||
const { languages, countries, agents, inboxes, teams, campaigns, labels } =
|
||||
params;
|
||||
switch (attribute_key) {
|
||||
case 'status':
|
||||
return getValuesForStatus(values);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
MessageMarkdownTransformer,
|
||||
MessageMarkdownSerializer,
|
||||
} from '@chatwoot/prosemirror-schema';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
/**
|
||||
* The delimiter used to separate the signature from the rest of the body.
|
||||
@@ -14,9 +15,25 @@ export const SIGNATURE_DELIMITER = '--';
|
||||
* Parse and Serialize the markdown text to remove any extra spaces or new lines
|
||||
*/
|
||||
export function cleanSignature(signature) {
|
||||
// convert from markdown to common mark format
|
||||
const nodes = new MessageMarkdownTransformer(messageSchema).parse(signature);
|
||||
return MessageMarkdownSerializer.serialize(nodes);
|
||||
try {
|
||||
// remove any horizontal rule tokens
|
||||
signature = signature
|
||||
.replace(/^( *\* *){3,} *$/gm, '')
|
||||
.replace(/^( *- *){3,} *$/gm, '')
|
||||
.replace(/^( *_ *){3,} *$/gm, '');
|
||||
|
||||
const nodes = new MessageMarkdownTransformer(messageSchema).parse(
|
||||
signature
|
||||
);
|
||||
return MessageMarkdownSerializer.serialize(nodes);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(e);
|
||||
Sentry.captureException(e);
|
||||
// The parser can break on some cases
|
||||
// for example, Token type `hr` not supported by Markdown parser
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,3 +156,62 @@ export function extractTextFromMarkdown(markdown) {
|
||||
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
||||
.trim(); // Trim any extra space
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the editor view into current cursor position
|
||||
*
|
||||
* @param {EditorView} view - The Prosemirror EditorView
|
||||
*
|
||||
*/
|
||||
export const scrollCursorIntoView = view => {
|
||||
// Get the current selection's head position (where the cursor is).
|
||||
const pos = view.state.selection.head;
|
||||
|
||||
// Get the corresponding DOM node for that position.
|
||||
const domAtPos = view.domAtPos(pos);
|
||||
const node = domAtPos.node;
|
||||
|
||||
// Scroll the node into view.
|
||||
if (node && node.scrollIntoView) {
|
||||
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a transaction that inserts a node into editor at the given position
|
||||
* Has an optional param 'content' to check if the
|
||||
*
|
||||
* @param {Node} node - The prosemirror node that needs to be inserted into the editor
|
||||
* @param {number} from - Position in the editor where the node needs to be inserted
|
||||
* @param {number} to - Position in the editor where the node needs to be replaced
|
||||
*
|
||||
*/
|
||||
export function insertAtCursor(editorView, node, from, to) {
|
||||
if (!editorView) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// This is a workaround to prevent inserting content into new line rather than on the exiting line
|
||||
// If the node is of type 'doc' and has only one child which is a paragraph,
|
||||
// then extract its inline content to be inserted as inline.
|
||||
const isWrappedInParagraph =
|
||||
node.type.name === 'doc' &&
|
||||
node.childCount === 1 &&
|
||||
node.firstChild.type.name === 'paragraph';
|
||||
|
||||
if (isWrappedInParagraph) {
|
||||
node = node.firstChild.content;
|
||||
}
|
||||
|
||||
let tr;
|
||||
if (to) {
|
||||
tr = editorView.state.tr.replaceWith(from, to, node).insertText(` `);
|
||||
} else {
|
||||
tr = editorView.state.tr.insert(from, node);
|
||||
}
|
||||
const state = editorView.state.apply(tr);
|
||||
editorView.updateState(state);
|
||||
editorView.focus();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// eslint-disable-next-line default-param-last
|
||||
export const getCurrentAccount = ({ accounts } = {}, accountId) => {
|
||||
return accounts.find(account => account.id === accountId);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
export const getUserRole = ({ accounts } = {}, accountId) => {
|
||||
const currentAccount = getCurrentAccount({ accounts }, accountId) || {};
|
||||
return currentAccount.role || null;
|
||||
|
||||
@@ -16,10 +16,8 @@ describe('Helper functions', () => {
|
||||
const changes = {
|
||||
role: [0, 1],
|
||||
};
|
||||
const {
|
||||
changes: extractedChanges,
|
||||
values,
|
||||
} = extractChangedAccountUserValues(changes);
|
||||
const { changes: extractedChanges, values } =
|
||||
extractChangedAccountUserValues(changes);
|
||||
expect(extractedChanges).toEqual(['role']);
|
||||
expect(values).toEqual(['administrator']);
|
||||
});
|
||||
@@ -28,10 +26,8 @@ describe('Helper functions', () => {
|
||||
const changes = {
|
||||
availability: [0, 2],
|
||||
};
|
||||
const {
|
||||
changes: extractedChanges,
|
||||
values,
|
||||
} = extractChangedAccountUserValues(changes);
|
||||
const { changes: extractedChanges, values } =
|
||||
extractChangedAccountUserValues(changes);
|
||||
expect(extractedChanges).toEqual(['availability']);
|
||||
expect(values).toEqual(['busy']);
|
||||
});
|
||||
@@ -41,10 +37,8 @@ describe('Helper functions', () => {
|
||||
role: [1, 0],
|
||||
availability: [1, 2],
|
||||
};
|
||||
const {
|
||||
changes: extractedChanges,
|
||||
values,
|
||||
} = extractChangedAccountUserValues(changes);
|
||||
const { changes: extractedChanges, values } =
|
||||
extractChangedAccountUserValues(changes);
|
||||
expect(extractedChanges).toEqual(['role', 'availability']);
|
||||
expect(values).toEqual(['agent', 'busy']);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,41 @@ import {
|
||||
replaceSignature,
|
||||
cleanSignature,
|
||||
extractTextFromMarkdown,
|
||||
insertAtCursor,
|
||||
} from '../editorHelper';
|
||||
import { EditorState } from 'prosemirror-state';
|
||||
import { EditorView } from 'prosemirror-view';
|
||||
import { Schema } from 'prosemirror-model';
|
||||
|
||||
// Define a basic ProseMirror schema
|
||||
const schema = new Schema({
|
||||
nodes: {
|
||||
doc: { content: 'paragraph+' },
|
||||
paragraph: {
|
||||
content: 'text*',
|
||||
toDOM: () => ['p', 0], // Represents a paragraph as a <p> tag in the DOM.
|
||||
},
|
||||
text: {
|
||||
toDOM: node => node.text, // Represents text as its actual string value.
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize a basic EditorState for testing
|
||||
const createEditorState = (content = '') => {
|
||||
if (!content) {
|
||||
return EditorState.create({
|
||||
schema,
|
||||
doc: schema.node('doc', null, [schema.node('paragraph')]),
|
||||
});
|
||||
}
|
||||
return EditorState.create({
|
||||
schema,
|
||||
doc: schema.node('doc', null, [
|
||||
schema.node('paragraph', null, [schema.text(content)]),
|
||||
]),
|
||||
});
|
||||
};
|
||||
|
||||
const NEW_SIGNATURE = 'This is a new signature';
|
||||
|
||||
@@ -85,6 +119,37 @@ describe('appendSignature', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanSignature', () => {
|
||||
it('removes any instance of horizontal rule', () => {
|
||||
const options = [
|
||||
'---',
|
||||
'***',
|
||||
'___',
|
||||
'- - -',
|
||||
'* * *',
|
||||
'_ _ _',
|
||||
' ---',
|
||||
'--- ',
|
||||
' --- ',
|
||||
'-----',
|
||||
'*****',
|
||||
'_____',
|
||||
'- - - -',
|
||||
'* * * * *',
|
||||
'_ _ _ _ _ _',
|
||||
' - - - - ',
|
||||
' * * * * * ',
|
||||
' _ _ _ _ _ _',
|
||||
'- - - - -',
|
||||
'* * * * * *',
|
||||
'_ _ _ _ _ _ _',
|
||||
];
|
||||
options.forEach(option => {
|
||||
expect(cleanSignature(option)).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSignature', () => {
|
||||
it('does not remove signature if not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
@@ -97,9 +162,8 @@ describe('removeSignature', () => {
|
||||
expect(removeSignature(body, signature)).toBe('This is a test\n\n');
|
||||
});
|
||||
it('removes signature if present with spaces and new lines', () => {
|
||||
const { body, signature } = HAS_SIGNATURE[
|
||||
'signature at end with spaces and new lines'
|
||||
];
|
||||
const { body, signature } =
|
||||
HAS_SIGNATURE['signature at end with spaces and new lines'];
|
||||
expect(removeSignature(body, signature)).toBe('This is a test\n\n');
|
||||
});
|
||||
it('removes signature if present without text before it', () => {
|
||||
@@ -129,9 +193,8 @@ describe('replaceSignature', () => {
|
||||
);
|
||||
});
|
||||
it('removes signature if present with spaces and new lines', () => {
|
||||
const { body, signature } = HAS_SIGNATURE[
|
||||
'signature at end with spaces and new lines'
|
||||
];
|
||||
const { body, signature } =
|
||||
HAS_SIGNATURE['signature at end with spaces and new lines'];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`This is a test\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
@@ -169,3 +232,44 @@ describe('extractTextFromMarkdown', () => {
|
||||
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertAtCursor', () => {
|
||||
it('should return undefined if editorView is not provided', () => {
|
||||
const result = insertAtCursor(undefined, schema.text('Hello'), 0);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should unwrap doc nodes that are wrapped in a paragraph', () => {
|
||||
const docNode = schema.node('doc', null, [
|
||||
schema.node('paragraph', null, [schema.text('Hello')]),
|
||||
]);
|
||||
|
||||
const editorState = createEditorState();
|
||||
const editorView = new EditorView(document.body, { state: editorState });
|
||||
|
||||
insertAtCursor(editorView, docNode, 0);
|
||||
|
||||
// Check if node was unwrapped and inserted correctly
|
||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello');
|
||||
});
|
||||
|
||||
it('should insert node without replacing any content if "to" is not provided', () => {
|
||||
const editorState = createEditorState();
|
||||
const editorView = new EditorView(document.body, { state: editorState });
|
||||
|
||||
insertAtCursor(editorView, schema.text('Hello'), 0);
|
||||
|
||||
// Check if node was inserted correctly
|
||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello');
|
||||
});
|
||||
|
||||
it('should replace content between "from" and "to" with the provided node', () => {
|
||||
const editorState = createEditorState('ReplaceMe');
|
||||
const editorView = new EditorView(document.body, { state: editorState });
|
||||
|
||||
insertAtCursor(editorView, schema.text('Hello'), 0, 8);
|
||||
|
||||
// Check if content was replaced correctly
|
||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello Me');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,16 @@
|
||||
"STATUS": "Status",
|
||||
"ORDER_BY": "Order by"
|
||||
},
|
||||
"CHAT_TIME_STAMP": {
|
||||
"CREATED": {
|
||||
"LATEST": "Created",
|
||||
"OLDEST": "Created at:"
|
||||
},
|
||||
"LAST_ACTIVITY": {
|
||||
"NOT_ACTIVE": "Last activity:",
|
||||
"ACTIVE": "Last activity"
|
||||
}
|
||||
},
|
||||
"CHAT_SORT_FILTER_ITEMS": {
|
||||
"latest": {
|
||||
"TEXT": "Last activity"
|
||||
|
||||
@@ -371,8 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Autenticándote con Facebook...",
|
||||
"ERROR_FB_AUTH": "Algo salió mal, Por favor actualiza la página...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"ERROR_FB_UNAUTHORIZED": "No estás autorizado a realizar esta acción. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Asegúrate de tener acceso a la página de Facebook con control completo. Puedes leer más sobre los roles de Facebook <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creando tu bandeja de entrada...",
|
||||
"TITLE": "Configurar detalles de la Bandeja de Entrada",
|
||||
"DESC": ""
|
||||
|
||||
@@ -57,15 +57,15 @@
|
||||
"DELETE": "%{agentName} a supprimé une macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
"ADD": "%{agentName} a ajouté %{user} à la boîte de réception (#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} a retiré %{user} de la boîte de réception (#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
"ADD": "%{agentName} a ajouté %{user} à la boîte de réception (#%{team_id})",
|
||||
"REMOVE": "%{agentName} a retiré %{user} de la boîte de réception (#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
"EDIT": "%{agentName} a mis à jour la configuration du compte (#%{id})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importer",
|
||||
"CANCEL": "Annuler"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"SUCCESS_MESSAGE": "Vous serez notifié par e-mail lorsque l'importation sera terminée.",
|
||||
"ERROR_MESSAGE": "Une erreur est survenue, veuillez réessayer"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"NO_INBOX_AGENT": "Oh Oh ! Il semble que vous ne faites parti d'aucune boîte de réception. Veuillez contacter votre administrateur",
|
||||
"SEARCH_MESSAGES": "Rechercher des messages dans les conversations",
|
||||
"EMPTY_STATE": {
|
||||
"CMD_BAR": "to open command menu",
|
||||
"KEYBOARD_SHORTCUTS": "to view keyboard shortcuts"
|
||||
"CMD_BAR": "pour ouvrir le menu de commande",
|
||||
"KEYBOARD_SHORTCUTS": "pour afficher les raccourcis clavier"
|
||||
},
|
||||
"SEARCH": {
|
||||
"TITLE": "Rechercher des messages",
|
||||
|
||||
@@ -111,8 +111,8 @@
|
||||
"ADD_LABEL": "Ajouter une étiquette à la conversation",
|
||||
"REMOVE_LABEL": "Supprimer l'étiquette de la conversation",
|
||||
"SETTINGS": "Paramètres",
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"APPEARANCE": "Appearance"
|
||||
"AI_ASSIST": "Assistance IA",
|
||||
"APPEARANCE": "Apparence"
|
||||
},
|
||||
"COMMANDS": {
|
||||
"GO_TO_CONVERSATION_DASHBOARD": "Aller au tableau de bord des conversations",
|
||||
@@ -134,7 +134,7 @@
|
||||
"GO_TO_NOTIFICATIONS": "Accéder aux notifications",
|
||||
"ADD_LABELS_TO_CONVERSATION": "Ajouter une étiquette à la conversation",
|
||||
"ASSIGN_AN_AGENT": "Affecter un agent",
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"AI_ASSIST": "Assistance IA",
|
||||
"ASSIGN_PRIORITY": "Attribuer priorité",
|
||||
"ASSIGN_A_TEAM": "Assigner une équipe",
|
||||
"MUTE_CONVERSATION": "Désactiver la conversation",
|
||||
@@ -150,9 +150,9 @@
|
||||
"UNTIL_NEXT_MONTH": "Jusqu'au mois prochain",
|
||||
"AN_HOUR_FROM_NOW": "D'ici une heure",
|
||||
"CUSTOM": "Personnalisé...",
|
||||
"CHANGE_APPEARANCE": "Change Appearance",
|
||||
"LIGHT_MODE": "Light",
|
||||
"DARK_MODE": "Dark",
|
||||
"CHANGE_APPEARANCE": "Changer l'apparence",
|
||||
"LIGHT_MODE": "Clair",
|
||||
"DARK_MODE": "Sombre",
|
||||
"SYSTEM_MODE": "Système"
|
||||
}
|
||||
},
|
||||
@@ -160,7 +160,7 @@
|
||||
"LOADING_MESSAGE": "Chargement de l'application du tableau de bord ..."
|
||||
},
|
||||
"COMMON": {
|
||||
"OR": "Or",
|
||||
"OR": "Ou",
|
||||
"CLICK_HERE": "cliquez ici"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +112,14 @@
|
||||
"ERROR": "Ce champ est requis"
|
||||
},
|
||||
"API_KEY": {
|
||||
"USE_API_KEY": "Use API Key Authentication",
|
||||
"LABEL": "API Key SID",
|
||||
"PLACEHOLDER": "Please enter your API Key SID",
|
||||
"USE_API_KEY": "Utiliser l'authentification par clé API",
|
||||
"LABEL": "SID de la clé API",
|
||||
"PLACEHOLDER": "Veuillez entrer votre clé API SID",
|
||||
"ERROR": "Ce champ est requis"
|
||||
},
|
||||
"API_KEY_SECRET": {
|
||||
"LABEL": "API Key Secret",
|
||||
"PLACEHOLDER": "Please enter your API Key Secret",
|
||||
"LABEL": "Secret de la clé API",
|
||||
"PLACEHOLDER": "Veuillez entrer le secret de votre clé API",
|
||||
"ERROR": "Ce champ est requis"
|
||||
},
|
||||
"MESSAGING_SERVICE_SID": {
|
||||
@@ -371,8 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authentification avec Facebook ...",
|
||||
"ERROR_FB_AUTH": "Une erreur s'est produite, veuillez rafraîchir la page ...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"ERROR_FB_UNAUTHORIZED": "Vous n'êtes pas autorisé à effectuer cette action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Veuillez vous assurer que vous avez un contrôle total sur la page Facebook. Vous pouvez en savoir plus sur les rôles Facebook <a href=\" https://www.facebook.com/help/187316341316631\">ici</a>.",
|
||||
"CREATING_CHANNEL": "Création de votre boîte de réception ...",
|
||||
"TITLE": "Configurer les détails de la boîte de réception",
|
||||
"DESC": ""
|
||||
@@ -405,21 +405,21 @@
|
||||
"DISABLED": "Désactivé"
|
||||
},
|
||||
"SENDER_NAME_SECTION": {
|
||||
"TITLE": "Sender name",
|
||||
"SUB_TEXT": "Select the name shown to the your customer when they receive emails from your agents.",
|
||||
"FOR_EG": "For eg:",
|
||||
"TITLE": "Nom de l'expéditeur",
|
||||
"SUB_TEXT": "Sélectionnez le nom présenté à votre client quand il reçoit des emails de vos agents.",
|
||||
"FOR_EG": "Par exemple:",
|
||||
"FRIENDLY": {
|
||||
"TITLE": "Amical",
|
||||
"FROM": "de",
|
||||
"SUBTITLE": "Add the name of the agent who sent the reply in the sender name to make it friendly."
|
||||
"SUBTITLE": "Ajoute le nom de l'agent qui a envoyé la réponse dans le nom de l'expéditeur pour la rendre plus conviviale."
|
||||
},
|
||||
"PROFESSIONAL": {
|
||||
"TITLE": "Professionnel",
|
||||
"SUBTITLE": "Use only the configured business name as the sender name in the email header."
|
||||
"SUBTITLE": "Utilisez uniquement le nom d'entreprise configuré comme nom d'expéditeur dans l'en-tête du courriel."
|
||||
},
|
||||
"BUSINESS_NAME": {
|
||||
"BUTTON_TEXT": "+ Configure your business name",
|
||||
"PLACEHOLDER": "Enter your business name",
|
||||
"BUTTON_TEXT": "+ Configurez votre nom d'entreprise",
|
||||
"PLACEHOLDER": "Entrez le nom de votre entreprise",
|
||||
"SAVE_BUTTON_TEXT": "Enregistrer"
|
||||
}
|
||||
},
|
||||
@@ -486,9 +486,9 @@
|
||||
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Activer ou désactiver la boîte de collecte des courriels pour les nouvelles conversations",
|
||||
"AUTO_ASSIGNMENT": "Activer l'assignation automatique",
|
||||
"ENABLE_CSAT": "Activer CSAT",
|
||||
"SENDER_NAME_SECTION": "Enable Agent Name in Email",
|
||||
"SENDER_NAME_SECTION": "Activer le nom de l'agent dans l'e-mail",
|
||||
"ENABLE_CSAT_SUB_TEXT": "Activer/Désactiver l'enquête CSAT(satisfaction du client) après avoir résolu une conversation",
|
||||
"SENDER_NAME_SECTION_TEXT": "Enable/Disable showing Agent's name in email, if disabled it will show business name",
|
||||
"SENDER_NAME_SECTION_TEXT": "Activer/Désactiver l'affichage du nom de l'agent dans l'e-mail, si désactivé, il affichera le nom de l'entreprise",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL": "Activer la continuité de la conversation par e-mail",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Les conversations se poursuivront par courrier électronique si l'adresse e-mail du contact est disponible.",
|
||||
"LOCK_TO_SINGLE_CONVERSATION": "Verrouiller à une seule conversation",
|
||||
|
||||
@@ -75,25 +75,25 @@
|
||||
"SLACK": {
|
||||
"DELETE": "Supprimer",
|
||||
"DELETE_CONFIRMATION": {
|
||||
"TITLE": "Delete the integration",
|
||||
"MESSAGE": "Are you sure you want to delete the integration? Doing so will result in the loss of access to conversations on your Slack workspace."
|
||||
"TITLE": "Supprimer l'intégration",
|
||||
"MESSAGE": "Êtes-vous sûr de vouloir supprimer l’intégration ? Cela entraînera la perte de l’accès aux conversations sur votre espace de travail Slack."
|
||||
},
|
||||
"HELP_TEXT": {
|
||||
"TITLE": "Utilisation de l'intégration Slack",
|
||||
"BODY": "<br/><p>Chatwoot va maintenant synchroniser toutes les conversations entrantes dans le canal <b><i>customer-conversations</i></b> à l'intérieur de Workspace Sack.</p><p>Répondre à un fil de conversation dans le canal Slack <b><i>customer-conversations</i></b> créera une réponse au client via chatwoot.</p><p>Commencez les réponses avec <b><i>note :</i></b> pour créer des notes privées au lieu de réponses.</p><p>Si le répondant sur Slack a un profil d'agent dans le chat avec la même adresse email, les réponses seront associées en conséquence.</p><p>Lorsque le répondant n'a pas de profil d'agent associé, les réponses seront faites avec le profil du bot.</p>",
|
||||
"SELECTED": "selected"
|
||||
"SELECTED": "sélectionné"
|
||||
},
|
||||
"SELECT_CHANNEL": {
|
||||
"OPTION_LABEL": "Select a channel",
|
||||
"OPTION_LABEL": "Sélectionnez un canal",
|
||||
"UPDATE": "Mettre à jour",
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
"BUTTON_TEXT": "Connecter le canal",
|
||||
"DESCRIPTION": "Votre espace de travail Slack est maintenant lié à Chatwoot. Cependant, l'intégration est actuellement inactive. Pour activer l'intégration et connecter un canal à Chatwoot, veuillez cliquer sur le bouton ci-dessous.\n\n**Note:** Si vous essayez de connecter un canal privé, ajoutez l'application Chatwoot au canal Slack avant de procéder à cette étape.",
|
||||
"ATTENTION_REQUIRED": "Attention requise",
|
||||
"EXPIRED": "Votre intégration Slack a expiré. Pour continuer à recevoir des messages sur Slack, veuillez supprimer l'intégration et connecter à nouveau votre espace de travail."
|
||||
},
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
"FAILED_TO_FETCH_CHANNELS": "There was an error fetching the channels from Slack, please try again"
|
||||
"UPDATE_ERROR": "Une erreur s'est produite lors de la mise à jour du contact, veuillez réessayer",
|
||||
"UPDATE_SUCCESS": "Le canal est connecté avec succès",
|
||||
"FAILED_TO_FETCH_CHANNELS": "Une erreur s'est produite lors de la récupération des informations, veuillez réessayer"
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Cliquez ici pour vous inscrire",
|
||||
@@ -103,39 +103,39 @@
|
||||
"CREATE_ERROR": "Une erreur s'est produite lors de la création du lien de réunion, veuillez réessayer"
|
||||
},
|
||||
"OPEN_AI": {
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"WITH_AI": " %{option} with AI ",
|
||||
"AI_ASSIST": "Assistance IA",
|
||||
"WITH_AI": " %{option} avec IA ",
|
||||
"OPTIONS": {
|
||||
"REPLY_SUGGESTION": "Reply Suggestion",
|
||||
"SUMMARIZE": "Summarize",
|
||||
"REPHRASE": "Improve Writing",
|
||||
"FIX_SPELLING_GRAMMAR": "Fix Spelling and Grammar",
|
||||
"SHORTEN": "Shorten",
|
||||
"EXPAND": "Expand",
|
||||
"MAKE_FRIENDLY": "Change message tone to friendly",
|
||||
"MAKE_FORMAL": "Use formal tone",
|
||||
"SIMPLIFY": "Simplify"
|
||||
"REPLY_SUGGESTION": "Suggestion de réponse",
|
||||
"SUMMARIZE": "Résumer",
|
||||
"REPHRASE": "Améliorer la rédaction",
|
||||
"FIX_SPELLING_GRAMMAR": "Corriger l'orthographe et la grammaire",
|
||||
"SHORTEN": "Raccourcir",
|
||||
"EXPAND": "Développer",
|
||||
"MAKE_FRIENDLY": "Modifier la tonalité du message en mode convivial",
|
||||
"MAKE_FORMAL": "Utiliser une tonalité formelle",
|
||||
"SIMPLIFY": "Simplifier"
|
||||
},
|
||||
"ASSISTANCE_MODAL": {
|
||||
"DRAFT_TITLE": "Draft content",
|
||||
"GENERATED_TITLE": "Generated content",
|
||||
"AI_WRITING": "AI is writing",
|
||||
"DRAFT_TITLE": "Contenu du brouillon",
|
||||
"GENERATED_TITLE": "Contenu généré",
|
||||
"AI_WRITING": "L'IA est en train d'écrire",
|
||||
"BUTTONS": {
|
||||
"APPLY": "Use this suggestion",
|
||||
"APPLY": "Utiliser cette suggestion",
|
||||
"CANCEL": "Annuler"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"TITLE": "Intégrer avec OpenAI",
|
||||
"DESC": "Apportez des fonctionnalités IA avancées à votre tableau de bord avec les modèles GPT d'OpenAI. Pour commencer, entrez la clé API de votre compte OpenAI.",
|
||||
"KEY_PLACEHOLDER": "Entrez votre clé API OpenAI",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "Besoin d'aide ?",
|
||||
"DISMISS": "Rejeter",
|
||||
"FINISH": "Finish Setup"
|
||||
"FINISH": "Terminer la configuration"
|
||||
},
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
"DISMISS_MESSAGE": "Vous pouvez configurer l'intégration OpenAI plus tard à chaque fois que vous le souhaitez.",
|
||||
"SUCCESS_MESSAGE": "Configuration de l'intégration OpenAI réussie"
|
||||
},
|
||||
"TITLE": "Améliorer avec l'IA",
|
||||
"SUMMARY_TITLE": "Résumé avec l'IA",
|
||||
|
||||
@@ -40,16 +40,16 @@
|
||||
},
|
||||
"SUGGESTIONS": {
|
||||
"TOOLTIP": {
|
||||
"SINGLE_SUGGESTION": "Add label to conversation",
|
||||
"MULTIPLE_SUGGESTION": "Select this label",
|
||||
"DESELECT": "Deselect label",
|
||||
"DISMISS": "Dismiss suggestion"
|
||||
"SINGLE_SUGGESTION": "Ajouter un libellé à la conversation",
|
||||
"MULTIPLE_SUGGESTION": "Sélectionner ce libellé",
|
||||
"DESELECT": "Désélectionner ce libellé",
|
||||
"DISMISS": "Rejeter la suggestion"
|
||||
},
|
||||
"POWERED_BY": "Chatwoot AI",
|
||||
"POWERED_BY": "Chatwoot IA",
|
||||
"DISMISS": "Rejeter",
|
||||
"ADD_SELECTED_LABELS": "Add selected labels",
|
||||
"ADD_SELECTED_LABEL": "Add selected label",
|
||||
"ADD_ALL_LABELS": "Add all labels"
|
||||
"ADD_SELECTED_LABELS": "Ajouter les libellés sélectionnés",
|
||||
"ADD_SELECTED_LABEL": "Ajouter le libellé sélectionné",
|
||||
"ADD_ALL_LABELS": "Ajouter tous les libellés"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Ajouter une étiquette",
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
"DESC": "(Total)"
|
||||
},
|
||||
"REPLY_TIME": {
|
||||
"NAME": "Customer waiting time",
|
||||
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} conversations)"
|
||||
"NAME": "Temps d'attente du client",
|
||||
"TOOLTIP_TEXT": "Le temps de résolution est %{metricValue} (basé sur %{conversationCount} conversations)"
|
||||
}
|
||||
},
|
||||
"DATE_RANGE_OPTIONS": {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"RESET_PASSWORD": {
|
||||
"TITLE": "Réinitialiser le mot de passe",
|
||||
"DESCRIPTION": "Enter the email address you use to log in to Chatwoot to get the password reset instructions.",
|
||||
"GO_BACK_TO_LOGIN": "If you want to go back to the login page,",
|
||||
"DESCRIPTION": "Entrez l'adresse e-mail que vous utilisez pour vous connecter à Chatwoot pour obtenir les instructions de réinitialisation du mot de passe.",
|
||||
"GO_BACK_TO_LOGIN": "Si vous voulez revenir à la page de connexion,",
|
||||
"EMAIL": {
|
||||
"LABEL": "Courriel",
|
||||
"PLACEHOLDER": "Veuillez saisir votre adresse de courriel.",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Signature du message personnel",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"NOTE": "Créez une signature de message unique qui apparaîtra à la fin de chaque message que vous envoyez à partir de n'importe quelle boîte de réception. Vous pouvez également inclure une image en ligne, qui est prise en charge dans les boîtes de réception en direct, les e-mails et les API.",
|
||||
"BTN_TEXT": "Enregistrer la signature du message",
|
||||
"API_ERROR": "Impossible d'enregistrer la signature ! Réessayez",
|
||||
"API_SUCCESS": "Signature enregistrée avec succès",
|
||||
@@ -152,7 +152,7 @@
|
||||
"SELECTOR_SUBTITLE": "Sélectionnez un compte dans la liste suivante",
|
||||
"PROFILE_SETTINGS": "Paramètres de profil",
|
||||
"KEYBOARD_SHORTCUTS": "Raccourcis clavier",
|
||||
"APPEARANCE": "Change Appearance",
|
||||
"APPEARANCE": "Changer l'apparence",
|
||||
"SUPER_ADMIN_CONSOLE": "Super console d'administration",
|
||||
"LOGOUT": "Se déconnecter"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"FILTER": {
|
||||
"TITLE": "Filter Conversations",
|
||||
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
|
||||
"EDIT_CUSTOM_FILTER": "Edit Folder",
|
||||
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
|
||||
"ADD_NEW_FILTER": "Add Filter",
|
||||
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
||||
"SUBMIT_BUTTON_LABEL": "Apply filters",
|
||||
"UPDATE_BUTTON_LABEL": "Update folder",
|
||||
"CANCEL_BUTTON_LABEL": "Cancel",
|
||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||
"EMPTY_VALUE_ERROR": "Value is required",
|
||||
"FOLDER_LABEL": "Folder Name",
|
||||
"FOLDER_QUERY_LABEL": "Folder Query",
|
||||
"TOOLTIP_LABEL": "Filter conversations",
|
||||
"QUERY_DROPDOWN_LABELS": {
|
||||
"AND": "AND",
|
||||
"OR": "OR"
|
||||
},
|
||||
"OPERATOR_LABELS": {
|
||||
"equal_to": "Equal to",
|
||||
"not_equal_to": "Not equal to",
|
||||
"contains": "Contains",
|
||||
"does_not_contain": "Does not contain",
|
||||
"is_present": "Is present",
|
||||
"is_not_present": "Is not present",
|
||||
"is_greater_than": "Is greater than",
|
||||
"is_less_than": "Is lesser than",
|
||||
"days_before": "Is x days before",
|
||||
"starts_with": "Starts with"
|
||||
},
|
||||
"ATTRIBUTE_LABELS": {
|
||||
"TRUE": "True",
|
||||
"FALSE": "False"
|
||||
},
|
||||
"ATTRIBUTES": {
|
||||
"STATUS": "Status",
|
||||
"ASSIGNEE_NAME": "Assignee Name",
|
||||
"INBOX_NAME": "Inbox Name",
|
||||
"TEAM_NAME": "Team Name",
|
||||
"CONVERSATION_IDENTIFIER": "Conversation Identifier",
|
||||
"CAMPAIGN_NAME": "Campaign Name",
|
||||
"LABELS": "Labels",
|
||||
"BROWSER_LANGUAGE": "Browser Language",
|
||||
"PRIORITY": "Priority",
|
||||
"COUNTRY_NAME": "Country Name",
|
||||
"REFERER_LINK": "Referer link",
|
||||
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
||||
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
||||
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
||||
"CREATED_AT": "Created At",
|
||||
"LAST_ACTIVITY": "Last Activity"
|
||||
},
|
||||
"GROUPS": {
|
||||
"STANDARD_FILTERS": "Standard Filters",
|
||||
"ADDITIONAL_FILTERS": "Additional Filters",
|
||||
"CUSTOM_ATTRIBUTES": "Custom Attributes"
|
||||
},
|
||||
"CUSTOM_VIEWS": {
|
||||
"ADD": {
|
||||
"TITLE": "Do you want to save this filter?",
|
||||
"LABEL": "Name this filter",
|
||||
"PLACEHOLDER": "Enter a name for this filter",
|
||||
"ERROR_MESSAGE": "Name is required",
|
||||
"SAVE_BUTTON": "Save filter",
|
||||
"CANCEL_BUTTON": "Cancel",
|
||||
"API_FOLDERS": {
|
||||
"SUCCESS_MESSAGE": "Folder created successfully",
|
||||
"ERROR_MESSAGE": "Error while creating folder"
|
||||
},
|
||||
"API_SEGMENTS": {
|
||||
"SUCCESS_MESSAGE": "Segment created successfully",
|
||||
"ERROR_MESSAGE": "Error while creating segment"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"EDIT_BUTTON": "Edit folder"
|
||||
},
|
||||
"DELETE": {
|
||||
"DELETE_BUTTON": "Delete filter",
|
||||
"MODAL": {
|
||||
"CONFIRM": {
|
||||
"TITLE": "Confirm Deletion",
|
||||
"MESSAGE": "Are you sure to delete the filter ",
|
||||
"YES": "Yes, Delete",
|
||||
"NO": "No, Keep it"
|
||||
}
|
||||
},
|
||||
"API_FOLDERS": {
|
||||
"SUCCESS_MESSAGE": "Folder deleted successfully",
|
||||
"ERROR_MESSAGE": "Error while deleting folder"
|
||||
},
|
||||
"API_SEGMENTS": {
|
||||
"SUCCESS_MESSAGE": "Segment deleted successfully",
|
||||
"ERROR_MESSAGE": "Error while deleting segment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"AGENT_BOTS": {
|
||||
"HEADER": "Bots",
|
||||
"LOADING_EDITOR": "Loading Editor...",
|
||||
"HEADER_BTN_TXT": "Add Bot Configuration",
|
||||
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
|
||||
"CSML_BOT_EDITOR": {
|
||||
"NAME": {
|
||||
"LABEL": "Bot Name",
|
||||
"PLACEHOLDER": "Give your bot a name",
|
||||
"ERROR": "Bot name is required"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Bot Description",
|
||||
"PLACEHOLDER": "What does this bot do?"
|
||||
},
|
||||
"BOT_CONFIG": {
|
||||
"ERROR": "Please enter your CSML bot configuration above",
|
||||
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
|
||||
},
|
||||
"SUBMIT": "Validate and save"
|
||||
},
|
||||
"BOT_CONFIGURATION": {
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "Update",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Configure new bot",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot added successfully",
|
||||
"ERROR_MESSAGE": "Could not add bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
|
||||
"LOADING": "Fetching Bots...",
|
||||
"TYPE": "Bot Type"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Delete",
|
||||
"TITLE": "Delete Bot",
|
||||
"SUBMIT": "Delete",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"BUTTON_TEXT": "Edit",
|
||||
"LOADING": "Fetching Bots...",
|
||||
"TITLE": "Edit Bot",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"TYPES": {
|
||||
"WEBHOOK": "Webhook Bot",
|
||||
"CSML": "CSML Bot"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"AGENT_MGMT": {
|
||||
"HEADER": "Agents",
|
||||
"HEADER_BTN_TXT": "Add Agent",
|
||||
"LOADING": "Fetching Agent List",
|
||||
"SIDEBAR_TXT": "<p><b>Agents</b></p> <p> An <b>Agent</b> is a member of your Customer Support team. </p><p> Agents will be able to view and reply to messages from your users. The list shows all agents currently in your account. </p><p> Click on <b>Add Agent</b> to add a new agent. Agent you add will receive an email with a confirmation link to activate their account, after which they can access Chatwoot and respond to messages. </p><p> Access to Chatwoot's features are based on following roles. </p><p> <b>Agent</b> - Agents with this role can only access inboxes, reports and conversations. They can assign conversations to other agents or themselves and resolve conversations.</p><p> <b>Administrator</b> - Administrator will have access to all Chatwoot features enabled for your account, including settings, along with all of a normal agents' privileges.</p>",
|
||||
"AGENT_TYPES": {
|
||||
"ADMINISTRATOR": "Administrator",
|
||||
"AGENT": "Agent"
|
||||
},
|
||||
"LIST": {
|
||||
"404": "There are no agents associated to this account",
|
||||
"TITLE": "Manage agents in your team",
|
||||
"DESC": "You can add/remove agents to/in your team.",
|
||||
"NAME": "Name",
|
||||
"EMAIL": "EMAIL",
|
||||
"STATUS": "Status",
|
||||
"ACTIONS": "Actions",
|
||||
"VERIFIED": "Verified",
|
||||
"VERIFICATION_PENDING": "Verification Pending"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Add agent to your team",
|
||||
"DESC": "You can add people who will be able to handle support for your inboxes.",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Agent Name",
|
||||
"PLACEHOLDER": "Please enter a name of the agent"
|
||||
},
|
||||
"AGENT_TYPE": {
|
||||
"LABEL": "Role",
|
||||
"PLACEHOLDER": "Please select a role",
|
||||
"ERROR": "Role is required"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Email Address",
|
||||
"PLACEHOLDER": "Please enter an email address of the agent"
|
||||
},
|
||||
"SUBMIT": "Add Agent"
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Agent added successfully",
|
||||
"EXIST_MESSAGE": "Agent email already in use, Please try another email address",
|
||||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Delete",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Agent deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||||
},
|
||||
"CONFIRM": {
|
||||
"TITLE": "Confirm Deletion",
|
||||
"MESSAGE": "Are you sure to delete ",
|
||||
"YES": "Yes, Delete ",
|
||||
"NO": "No, Keep "
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit agent",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Agent Name",
|
||||
"PLACEHOLDER": "Please enter a name of the agent"
|
||||
},
|
||||
"AGENT_TYPE": {
|
||||
"LABEL": "Role",
|
||||
"PLACEHOLDER": "Please select a role",
|
||||
"ERROR": "Role is required"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Email Address",
|
||||
"PLACEHOLDER": "Please enter an email address of the agent"
|
||||
},
|
||||
"AGENT_AVAILABILITY": {
|
||||
"LABEL": "Availability",
|
||||
"PLACEHOLDER": "Please select an availability status",
|
||||
"ERROR": "Availability is required"
|
||||
},
|
||||
"SUBMIT": "Edit Agent"
|
||||
},
|
||||
"BUTTON_TEXT": "Edit",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Agent updated successfully",
|
||||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||||
},
|
||||
"PASSWORD_RESET": {
|
||||
"ADMIN_RESET_BUTTON": "Reset Password",
|
||||
"ADMIN_SUCCESS_MESSAGE": "An email with reset password instructions has been sent to the agent",
|
||||
"SUCCESS_MESSAGE": "Agent password reset successfully",
|
||||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||||
}
|
||||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": "No results found."
|
||||
},
|
||||
"MULTI_SELECTOR": {
|
||||
"PLACEHOLDER": "None",
|
||||
"TITLE": {
|
||||
"AGENT": "Select agent",
|
||||
"TEAM": "Select team"
|
||||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": {
|
||||
"AGENT": "No agents found",
|
||||
"TEAM": "No teams found"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"AGENT": "Search agents",
|
||||
"TEAM": "Search teams",
|
||||
"INPUT": "Search for agents"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"ATTRIBUTES_MGMT": {
|
||||
"HEADER": "Custom Attributes",
|
||||
"HEADER_BTN_TXT": "Add Custom Attribute",
|
||||
"LOADING": "Fetching custom attributes",
|
||||
"SIDEBAR_TXT": "<p><b>Custom Attributes</b> <p>A custom attribute tracks facts about your contacts/conversation — like the subscription plan, or when they ordered the first item etc. <br /><br />For creating a Custom Attribute, just click on the <b>Add Custom Attribute.</b> You can also edit or delete an existing Custom Attribute by clicking on the Edit or Delete button.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Add Custom Attribute",
|
||||
"SUBMIT": "Create",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Display Name",
|
||||
"PLACEHOLDER": "Enter custom attribute display name",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter custom attribute description",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"MODEL": {
|
||||
"LABEL": "Applies to",
|
||||
"PLACEHOLDER": "Please select one",
|
||||
"ERROR": "Model is required"
|
||||
},
|
||||
"TYPE": {
|
||||
"LABEL": "Type",
|
||||
"PLACEHOLDER": "Please select a type",
|
||||
"ERROR": "Type is required",
|
||||
"LIST": {
|
||||
"LABEL": "List Values",
|
||||
"PLACEHOLDER": "Please enter value and press enter key",
|
||||
"ERROR": "Must have at least one value"
|
||||
}
|
||||
},
|
||||
"KEY": {
|
||||
"LABEL": "Key",
|
||||
"PLACEHOLDER": "Enter custom attribute key",
|
||||
"ERROR": "Key is required",
|
||||
"IN_VALID": "Invalid key"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully!",
|
||||
"ERROR_MESSAGE": "Could not create a Custom Attribute. Please try again later."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Delete",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute deleted successfully.",
|
||||
"ERROR_MESSAGE": "Couldn't delete the custom attribute. Try again."
|
||||
},
|
||||
"CONFIRM": {
|
||||
"TITLE": "Are you sure want to delete - %{attributeName}",
|
||||
"PLACE_HOLDER": "Please type {attributeName} to confirm",
|
||||
"MESSAGE": "Deleting will remove the custom attribute",
|
||||
"YES": "Delete ",
|
||||
"NO": "Cancel"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit Custom Attribute",
|
||||
"UPDATE_BUTTON_TEXT": "Update",
|
||||
"TYPE": {
|
||||
"LIST": {
|
||||
"LABEL": "List Values",
|
||||
"PLACEHOLDER": "Please enter values and press enter key"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute updated successfully",
|
||||
"ERROR_MESSAGE": "There was an error updating custom attribute, please try again"
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"HEADER": "Custom Attributes",
|
||||
"CONVERSATION": "Conversation",
|
||||
"CONTACT": "Contact"
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Name",
|
||||
"Description",
|
||||
"Type",
|
||||
"Key"
|
||||
],
|
||||
"BUTTONS": {
|
||||
"EDIT": "Edit",
|
||||
"DELETE": "Delete"
|
||||
},
|
||||
"EMPTY_RESULT": {
|
||||
"404": "There are no custom attributes created",
|
||||
"NOT_FOUND": "There are no custom attributes configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"AUDIT_LOGS": {
|
||||
"HEADER": "Audit Logs",
|
||||
"HEADER_BTN_TXT": "Add Audit Logs",
|
||||
"LOADING": "Fetching Audit Logs",
|
||||
"SEARCH_404": "There are no items matching this query",
|
||||
"SIDEBAR_TXT": "<p><b>Audit Logs</b> </p><p> Audit Logs are trails for events and actions in a Chatwoot System. </p>",
|
||||
"LIST": {
|
||||
"404": "There are no Audit Logs available in this account.",
|
||||
"TITLE": "Manage Audit Logs",
|
||||
"DESC": "Audit Logs are trails for events and actions in a Chatwoot System.",
|
||||
"TABLE_HEADER": [
|
||||
"Activity",
|
||||
"Time",
|
||||
"IP Address"
|
||||
]
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "AuditLogs retrieved successfully",
|
||||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||||
},
|
||||
"DEFAULT_USER": "System",
|
||||
"AUTOMATION_RULE": {
|
||||
"ADD": "%{agentName} created a new automation rule (#%{id})",
|
||||
"EDIT": "%{agentName} updated an automation rule (#%{id})",
|
||||
"DELETE": "%{agentName} deleted an automation rule (#%{id})"
|
||||
},
|
||||
"ACCOUNT_USER": {
|
||||
"ADD": "%{agentName} invited %{invitee} to the account as an %{role}",
|
||||
"EDIT": {
|
||||
"SELF": "%{agentName} changed their %{attributes} to %{values}",
|
||||
"OTHER": "%{agentName} changed %{attributes} of %{user} to %{values}"
|
||||
}
|
||||
},
|
||||
"INBOX": {
|
||||
"ADD": "%{agentName} created a new inbox (#%{id})",
|
||||
"EDIT": "%{agentName} updated an inbox (#%{id})",
|
||||
"DELETE": "%{agentName} deleted an inbox (#%{id})"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"ADD": "%{agentName} created a new webhook (#%{id})",
|
||||
"EDIT": "%{agentName} updated a webhook (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a webhook (#%{id})"
|
||||
},
|
||||
"USER_ACTION": {
|
||||
"SIGN_IN": "%{agentName} signed in",
|
||||
"SIGN_OUT": "%{agentName} signed out"
|
||||
},
|
||||
"TEAM": {
|
||||
"ADD": "%{agentName} created a new team (#%{id})",
|
||||
"EDIT": "%{agentName} updated a team (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a team (#%{id})"
|
||||
},
|
||||
"MACRO": {
|
||||
"ADD": "%{agentName} created a new macro (#%{id})",
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user