diff --git a/.rubocop.yml b/.rubocop.yml
index 01951faea..0fca6f54b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -105,6 +105,7 @@ Rails/UniqueValidationWithoutIndex:
Exclude:
- 'app/models/channel/twitter_profile.rb'
- 'app/models/webhook.rb'
+ - 'app/models/contact.rb'
Rails/RenderInline:
Exclude:
- 'app/controllers/swagger_controller.rb'
diff --git a/README.md b/README.md
index bf868799f..030b13dcd 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
-
+
-
A simple and elegant live chat software
- An opensource alternative to Intercom, Zendesk, Drift, Crisp etc.
+ Customer engagement suite, an open-source alternative to Intercom, Zendesk, Salesforce Service Cloud etc.
@@ -16,11 +15,13 @@ ___
+
+
diff --git a/app/assets/stylesheets/administrate/utilities/_variables.scss b/app/assets/stylesheets/administrate/utilities/_variables.scss
index 1798d918b..c46326612 100644
--- a/app/assets/stylesheets/administrate/utilities/_variables.scss
+++ b/app/assets/stylesheets/administrate/utilities/_variables.scss
@@ -55,7 +55,7 @@ $color-heading: #1f2d3d;
$color-extra-light-blue: #f5f7f9;
$primary-color: $color-woot;
-$secondary-color: #35c5ff;
+$secondary-color: #5d7592;
$success-color: #44ce4b;
$warning-color: #ffc532;
$alert-color: #ff382d;
diff --git a/app/builders/contact_inbox_builder.rb b/app/builders/contact_inbox_builder.rb
new file mode 100644
index 000000000..73380190d
--- /dev/null
+++ b/app/builders/contact_inbox_builder.rb
@@ -0,0 +1,41 @@
+class ContactInboxBuilder
+ pattr_initialize [:contact_id!, :inbox_id!, :source_id]
+
+ def perform
+ @contact = Contact.find(contact_id)
+ @inbox = @contact.account.inboxes.find(inbox_id)
+ return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api'].include? @inbox.channel_type
+
+ source_id = @source_id || generate_source_id
+ create_contact_inbox(source_id) if source_id.present?
+ end
+
+ private
+
+ def generate_source_id
+ return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms'
+ return @contact.email if @inbox.channel_type == 'Channel::Email'
+ return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api'
+
+ nil
+ end
+
+ def twilio_source_id
+ return unless @contact.phone_number
+
+ case @inbox.channel.medium
+ when 'sms'
+ @contact.phone_number
+ when 'whatsapp'
+ "whatsapp:#{@contact.phone_number}"
+ end
+ end
+
+ def create_contact_inbox(source_id)
+ ::ContactInbox.find_or_create_by!(
+ contact_id: @contact.id,
+ inbox_id: @inbox.id,
+ source_id: source_id
+ )
+ end
+end
diff --git a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb
index 8a9199b6b..576fbaa2f 100644
--- a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb
@@ -8,10 +8,10 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::
private
def inbox_ids
- if current_user.administrator?
+ if Current.user.administrator?
Current.account.inboxes.pluck(:id)
- elsif current_user.agent?
- current_user.assigned_inboxes.pluck(:id)
+ elsif Current.user.agent?
+ Current.user.assigned_inboxes.pluck(:id)
else
[]
end
diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb
index 37a583f05..375eb9313 100644
--- a/app/controllers/api/v1/accounts/contacts_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts_controller.rb
@@ -4,7 +4,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :set_current_page, only: [:index, :active, :search]
- before_action :fetch_contact, only: [:show, :update]
+ before_action :fetch_contact, only: [:show, :update, :contactable_inboxes]
def index
@contacts_count = resolved_contacts.count
@@ -40,6 +40,10 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
def show; end
+ def contactable_inboxes
+ @contactable_inboxes = Contacts::ContactableInboxesService.new(contact: @contact).get
+ end
+
def create
ActiveRecord::Base.transaction do
@contact = Current.account.contacts.new(contact_params)
diff --git a/app/controllers/api/v1/accounts/conversations/messages_controller.rb b/app/controllers/api/v1/accounts/conversations/messages_controller.rb
index 8cfea20ba..ffd00461b 100644
--- a/app/controllers/api/v1/accounts/conversations/messages_controller.rb
+++ b/app/controllers/api/v1/accounts/conversations/messages_controller.rb
@@ -4,7 +4,7 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
end
def create
- user = current_user || @resource
+ user = Current.user || @resource
mb = Messages::MessageBuilder.new(user, @conversation, params)
@message = mb.perform
rescue StandardError => e
diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb
index 5dddf8036..dccb56a95 100644
--- a/app/controllers/api/v1/accounts/conversations_controller.rb
+++ b/app/controllers/api/v1/accounts/conversations_controller.rb
@@ -22,7 +22,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def create
- @conversation = ::Conversation.create!(conversation_params)
+ ActiveRecord::Base.transaction do
+ @conversation = ::Conversation.create!(conversation_params)
+ Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present?
+ end
end
def show; end
@@ -78,16 +81,29 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def contact_inbox
+ @contact_inbox = build_contact_inbox
+
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
end
+ def build_contact_inbox
+ return if params[:contact_id].blank? || params[:inbox_id].blank?
+
+ ContactInboxBuilder.new(
+ contact_id: params[:contact_id],
+ inbox_id: params[:inbox_id],
+ source_id: params[:source_id]
+ ).perform
+ end
+
def conversation_params
+ additional_attributes = params[:additional_attributes]&.permit! || {}
{
account_id: Current.account.id,
inbox_id: @contact_inbox.inbox_id,
contact_id: @contact_inbox.contact_id,
contact_inbox_id: @contact_inbox.id,
- additional_attributes: params[:additional_attributes]
+ additional_attributes: additional_attributes
}
end
diff --git a/app/controllers/concerns/access_token_auth_helper.rb b/app/controllers/concerns/access_token_auth_helper.rb
index 3d6f55674..91bd461aa 100644
--- a/app/controllers/concerns/access_token_auth_helper.rb
+++ b/app/controllers/concerns/access_token_auth_helper.rb
@@ -14,6 +14,7 @@ module AccessTokenAuthHelper
render_unauthorized('Invalid Access Token') && return if @access_token.blank?
@resource = @access_token.owner
+ Current.user = @resource if current_user.is_a?(User)
end
def super_admin?
@@ -21,7 +22,7 @@ module AccessTokenAuthHelper
end
def validate_bot_access_token!
- return if current_user.is_a?(User)
+ return if Current.user.is_a?(User)
return if super_admin?
return if agent_bot_accessible?
diff --git a/app/javascript/dashboard/api/auth.js b/app/javascript/dashboard/api/auth.js
index 5de3e8d9b..4a1ca37f1 100644
--- a/app/javascript/dashboard/api/auth.js
+++ b/app/javascript/dashboard/api/auth.js
@@ -61,7 +61,9 @@ export default {
},
isLoggedIn() {
- return !!Cookies.getJSON('auth_data');
+ const hasAuthCookie = !!Cookies.getJSON('auth_data');
+ const hasUserCookie = !!Cookies.getJSON('user');
+ return hasAuthCookie && hasUserCookie;
},
isAdmin() {
@@ -79,7 +81,9 @@ export default {
},
getPubSubToken() {
if (this.isLoggedIn()) {
- return Cookies.getJSON('user').pubsub_token;
+ const user = Cookies.getJSON('user') || {};
+ const { pubsub_token: pubsubToken } = user;
+ return pubsubToken;
}
return null;
},
diff --git a/app/javascript/dashboard/api/contacts.js b/app/javascript/dashboard/api/contacts.js
index 6429c53d2..5dd0bcc57 100644
--- a/app/javascript/dashboard/api/contacts.js
+++ b/app/javascript/dashboard/api/contacts.js
@@ -14,6 +14,10 @@ class ContactAPI extends ApiClient {
return axios.get(`${this.url}/${contactId}/conversations`);
}
+ getContactableInboxes(contactId) {
+ return axios.get(`${this.url}/${contactId}/contactable_inboxes`);
+ }
+
search(search = '', page = 1) {
return axios.get(`${this.url}/search?q=${search}&page=${page}`);
}
diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js
index b520156a4..9388fc3bb 100644
--- a/app/javascript/dashboard/api/inbox/conversation.js
+++ b/app/javascript/dashboard/api/inbox/conversation.js
@@ -28,8 +28,10 @@ class ConversationApi extends ApiClient {
});
}
- toggleStatus(conversationId) {
- return axios.post(`${this.url}/${conversationId}/toggle_status`, {});
+ toggleStatus({ conversationId, status }) {
+ return axios.post(`${this.url}/${conversationId}/toggle_status`, {
+ status,
+ });
}
assignAgent({ conversationId, agentId }) {
diff --git a/app/javascript/dashboard/assets/scss/_foundation-settings.scss b/app/javascript/dashboard/assets/scss/_foundation-settings.scss
index e8862b5bf..8d6e63df9 100644
--- a/app/javascript/dashboard/assets/scss/_foundation-settings.scss
+++ b/app/javascript/dashboard/assets/scss/_foundation-settings.scss
@@ -49,7 +49,7 @@ $global-font-size: 10px;
$global-width: 100%;
$global-lineheight: 1.5;
$foundation-palette: (primary: $color-woot,
- secondary: #35c5ff,
+ secondary: #5d7592,
success: #44ce4b,
warning: #ffc532,
alert: #ff382d);
@@ -260,11 +260,11 @@ color 0.25s ease-out;
// 12. Button Group
// ----------------
-$buttongroup-margin: 1rem;
-$buttongroup-spacing: 1px;
+$buttongroup-margin: 0;
+$buttongroup-spacing: 0;
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
-$buttongroup-radius-on-each: true;
+$buttongroup-radius-on-each: false;
// 13. Callout
// -----------
diff --git a/app/javascript/dashboard/assets/scss/_variables.scss b/app/javascript/dashboard/assets/scss/_variables.scss
index 0e8b948c4..447e86a25 100644
--- a/app/javascript/dashboard/assets/scss/_variables.scss
+++ b/app/javascript/dashboard/assets/scss/_variables.scss
@@ -55,7 +55,7 @@ $color-heading: #1f2d3d;
$color-extra-light-blue: #f5f7f9;
$primary-color: $color-woot;
-$secondary-color: #35c5ff;
+$secondary-color: #5d7592;
$success-color: #44ce4b;
$warning-color: #ffc532;
$alert-color: #ff382d;
diff --git a/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss b/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss
index 9f70dec13..7009a077f 100644
--- a/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss
+++ b/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss
@@ -1,6 +1,8 @@
.dropdown-pane {
@include elegant-card;
@include border-light;
+ box-sizing: content-box;
+ width: fit-content;
z-index: 999;
&.dropdown-pane--open {
diff --git a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
index 36df6af14..bc024e024 100644
--- a/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
+++ b/app/javascript/dashboard/assets/scss/plugins/_multiselect.scss
@@ -118,6 +118,8 @@
}
.multiselect__single {
+ @include text-ellipsis;
+ display: inline-block;
margin-bottom: 0;
padding: var(--space-slab) var(--space-one);
}
diff --git a/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss b/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss
index 69363e542..dc2a4c2d1 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_conv-header.scss
@@ -8,20 +8,15 @@ $resolve-button-width: 13.2rem;
@include flex-align($x: justify, $y: middle);
@include border-normal-bottom;
- // Resolve Button
- .button {
- @include margin(0);
- @include flex;
- }
-
.multiselect-box {
@include flex;
@include flex-align($x: justify, $y: middle);
- @include border-light;
- border-radius: $space-smaller;
+ border: 1px solid var(--color-border);
+ border-radius: var(--space-smaller);
margin-right: var(--space-small);
+ width: 20.2rem;
- &::before {
+ .icon {
color: $medium-gray;
font-size: $font-size-default;
line-height: 3.8rem;
@@ -31,6 +26,7 @@ $resolve-button-width: 13.2rem;
.multiselect {
margin: 0;
+ min-width: 0;
.multiselect__tags {
border: 0;
@@ -66,26 +62,6 @@ $resolve-button-width: 13.2rem;
}
}
-.button.resolve--button {
- @include flex-align($x: center, $y: middle);
- font-size: var(--font-size-default);
- width: $resolve-button-width;
-
- >.icon {
- font-size: $font-size-default;
- padding-right: $space-small;
- }
-
- .spinner {
- margin-right: $space-smaller;
- padding: 0 $space-one;
-
- &::before {
- border-top-color: $color-white;
- }
- }
-}
-
.header-actions-wrap {
display: flex;
diff --git a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss
index 297166b66..56f75ab76 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_conversation-view.scss
@@ -151,6 +151,7 @@
border-top-left-radius: $space-smaller;
color: $color-body;
margin-right: auto;
+ word-break: break-word;
&.is-image {
border-radius: var(--border-radius-large);
@@ -198,6 +199,7 @@
border-bottom-right-radius: $space-smaller;
border-top-right-radius: $space-smaller;
margin-left: auto;
+ word-break: break-word;
&.is-private {
background: lighten($warning-color, 32%);
@@ -228,6 +230,10 @@
}
}
+ &.center {
+ justify-content: center;
+ }
+
.wrap {
@include margin($zero $space-normal);
max-width: 69%;
@@ -247,17 +253,22 @@
}
.activity-wrap {
- @include flex;
- @include margin($space-small auto);
- @include padding($space-small $space-normal);
- @include flex-align($x: center, $y: null);
- background: lighten($warning-color, 32%);
- border: 1px solid lighten($warning-color, 22%);
- border-radius: $space-smaller;
- font-size: $font-size-small;
+ background: var(--s-50);
+ border: 1px solid var(--s-100);
+ border-radius: var(--border-radius-medium);
+ display: flex;
+ font-size: var(--font-size-small);
+ justify-content: center;
+ margin: var(--space-small) var(--space-normal);
+ padding: var(--space-small) var(--space-normal);
- .message-text__wrap {
+ .is-text {
display: inline-block;
+ text-align: start;
+
+ @include breakpoint(xxxlarge up) {
+ display: flex;
+ }
}
}
}
diff --git a/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss b/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
index 099d32287..aafff16ff 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
@@ -67,12 +67,15 @@
flex-direction: column;
position: relative;
+ &:hover {
+ background: var(--color-background-light);
+ }
+
.dropdown-pane {
bottom: 6rem;
display: block;
- left: 5rem;
visibility: visible;
- width: 80%;
+ width: fit-content;
}
.active {
diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue
index a21fb15a7..392b0a7a4 100644
--- a/app/javascript/dashboard/components/ChatList.vue
+++ b/app/javascript/dashboard/components/ChatList.vue
@@ -3,7 +3,6 @@
-
{{ pageTitle }}
diff --git a/app/javascript/dashboard/components/SidemenuIcon.vue b/app/javascript/dashboard/components/SidemenuIcon.vue
index 1ac0b0370..f17a1c865 100644
--- a/app/javascript/dashboard/components/SidemenuIcon.vue
+++ b/app/javascript/dashboard/components/SidemenuIcon.vue
@@ -3,7 +3,6 @@
+
diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue
index 6127eec43..9b3274e31 100644
--- a/app/javascript/dashboard/components/buttons/ResolveAction.vue
+++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue
@@ -1,29 +1,86 @@
-
+
+
+ toggleStatus(STATUS_TYPE.RESOLVED)"
+ >
+ {{ this.$t('CONVERSATION.HEADER.RESOLVE_ACTION') }}
+
+ toggleStatus(STATUS_TYPE.OPEN)"
+ >
+ {{ this.$t('CONVERSATION.HEADER.REOPEN_ACTION') }}
+
+ toggleStatus(STATUS_TYPE.OPEN)"
+ >
+ {{ this.$t('CONVERSATION.HEADER.OPEN_ACTION') }}
+
+
+
+
+
+
+
+ toggleStatus(STATUS_TYPE.BOT)"
+ >
+ {{ this.$t('CONVERSATION.RESOLVE_DROPDOWN.OPEN_BOT') }}
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components/index.js b/app/javascript/dashboard/components/index.js
index fff5ff899..239fbd809 100644
--- a/app/javascript/dashboard/components/index.js
+++ b/app/javascript/dashboard/components/index.js
@@ -2,7 +2,7 @@
/* eslint-env browser */
import AvatarUploader from './widgets/forms/AvatarUploader.vue';
import Bar from './widgets/chart/BarChart';
-import Button from './widgets/Button';
+import Button from './ui/WootButton';
import Code from './Code';
import ColorPicker from './widgets/ColorPicker';
import DeleteModal from './widgets/modal/DeleteModal.vue';
diff --git a/app/javascript/dashboard/components/layout/AvailabilityStatus.vue b/app/javascript/dashboard/components/layout/AvailabilityStatus.vue
index 469129339..0be471a46 100644
--- a/app/javascript/dashboard/components/layout/AvailabilityStatus.vue
+++ b/app/javascript/dashboard/components/layout/AvailabilityStatus.vue
@@ -170,6 +170,7 @@ export default {
.status-change {
.dropdown-pane {
top: -132px;
+ right: var(--space-normal);
}
.status-items {
diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue
index 0bb416456..509c34696 100644
--- a/app/javascript/dashboard/components/layout/Sidebar.vue
+++ b/app/javascript/dashboard/components/layout/Sidebar.vue
@@ -19,12 +19,12 @@
:menu-item="teamSection"
/>
@@ -128,11 +128,11 @@ export default {
currentRoute() {
return this.$store.state.route.name;
},
- shouldShowInboxes() {
+ shouldShowSidebarItem() {
return this.sidemenuItems.common.routes.includes(this.currentRoute);
},
shouldShowTeams() {
- return this.shouldShowInboxes && this.teams.length;
+ return this.shouldShowSidebarItem && this.teams.length;
},
inboxSection() {
return {
diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue b/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue
index a2902d071..292e4df4e 100644
--- a/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue
+++ b/app/javascript/dashboard/components/layout/sidebarComponents/OptionsMenu.vue
@@ -80,3 +80,8 @@ export default {
},
};
+
diff --git a/app/javascript/dashboard/components/ui/WootButton.vue b/app/javascript/dashboard/components/ui/WootButton.vue
index e97f41f41..4df156f3a 100644
--- a/app/javascript/dashboard/components/ui/WootButton.vue
+++ b/app/javascript/dashboard/components/ui/WootButton.vue
@@ -12,8 +12,8 @@
@click="handleClick"
>
-
-
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue
index 84d6c7277..2d5ded515 100644
--- a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue
+++ b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue
@@ -11,9 +11,7 @@
class="image-thumb"
:src="attachment.thumb"
/>
-
- 📄
-
+
📄
@@ -37,8 +35,7 @@
diff --git a/app/javascript/dashboard/helper/files.js b/app/javascript/dashboard/helper/files.js
deleted file mode 100644
index 4f7559304..000000000
--- a/app/javascript/dashboard/helper/files.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export const formatBytes = (bytes, decimals = 2) => {
- if (bytes === 0) return '0 Bytes';
-
- const k = 1024;
- const dm = decimals < 0 ? 0 : decimals;
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-
- const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
-};
diff --git a/app/javascript/dashboard/helper/specs/files.spec.js b/app/javascript/dashboard/helper/specs/files.spec.js
deleted file mode 100644
index 3b2c86bcd..000000000
--- a/app/javascript/dashboard/helper/specs/files.spec.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { formatBytes } from '../files';
-
-describe('#File Helpers', () => {
- describe('formatBytes', () => {
- it('should return zero bytes if 0 is passed', () => {
- expect(formatBytes(0)).toBe('0 Bytes');
- });
- it('should return in bytes if 1000 is passed', () => {
- expect(formatBytes(1000)).toBe('1000 Bytes');
- });
- it('should return in KB if 100000 is passed', () => {
- expect(formatBytes(10000)).toBe('9.77 KB');
- });
- it('should return in MB if 10000000 is passed', () => {
- expect(formatBytes(10000000)).toBe('9.54 MB');
- });
- });
-});
diff --git a/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json
index b08032106..e9b0fc91c 100644
--- a/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/el/inboxMgmt.json
@@ -88,7 +88,7 @@
"SUBMIT_BUTTON": "Δημιουργία Κιβωτίου"
},
"TWILIO": {
- "TITLE": "SMS κανάλι από το Twillio",
+ "TITLE": "SMS κανάλι από το Twilio",
"DESC": "Ενσωματώστε το Twilio και αρχίστε να υποστηρίζετε τους πελάτες σας μέσω SMS.",
"ACCOUNT_SID": {
"LABEL": "SID Λογαριασμού",
@@ -116,7 +116,7 @@
},
"API_CALLBACK": {
"TITLE": "URL επανάκλησης",
- "SUBTITLE": "Πρέπει να ρυθμίσετε το callback URL στο Twillio με το URL που αναφέρεται εδώ."
+ "SUBTITLE": "Πρέπει να ρυθμίσετε το callback URL στο Twilio με το URL που αναφέρεται εδώ."
},
"SUBMIT_BUTTON": "Δημιουργία Καναλιού Twillio",
"API": {
diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json
index d6cc103ca..b8d936d22 100644
--- a/app/javascript/dashboard/i18n/locale/en/contact.json
+++ b/app/javascript/dashboard/i18n/locale/en/contact.json
@@ -12,6 +12,7 @@
"INITIATED_FROM": "Initiated from",
"INITIATED_AT": "Initiated at",
"IP_ADDRESS": "IP Address",
+ "NEW_MESSAGE": "New message",
"CONVERSATIONS": {
"NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.",
"TITLE": "Previous Conversations"
@@ -71,7 +72,9 @@
},
"PHONE_NUMBER": {
"PLACEHOLDER": "Enter the phone number of the contact",
- "LABEL": "Phone Number"
+ "LABEL": "Phone Number",
+ "HELP": "Phone number should be of E.164 format eg: +1415555555 [+][country code][area code][local phone number]",
+ "ERROR": "Phone number should be either empty or of E.164 format"
},
"LOCATION": {
"PLACEHOLDER": "Enter the location of the contact",
@@ -104,6 +107,30 @@
"CONTACT_ALREADY_EXIST": "This email address is in use for another contact.",
"ERROR_MESSAGE": "There was an error, please try again"
},
+ "NEW_CONVERSATION": {
+ "BUTTON_LABEL": "Start conversation",
+ "TITLE": "New conversation",
+ "DESC": "Start a new conversation by sending a new message.",
+ "NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
+ "FORM": {
+ "TO": {
+ "LABEL": "To"
+ },
+ "INBOX": {
+ "LABEL": "Inbox",
+ "ERROR": "Select an inbox"
+ },
+ "MESSAGE": {
+ "LABEL": "Message",
+ "PLACEHOLDER": "Write your message here",
+ "ERROR": "Message can't be empty"
+ },
+ "SUBMIT": "Send message",
+ "CANCEL": "Cancel",
+ "SUCCESS_MESSAGE": "Message sent!",
+ "ERROR_MESSAGE": "Couldn't send! try again"
+ }
+ },
"CONTACTS_PAGE": {
"HEADER": "Contacts",
"SEARCH_BUTTON": "Search",
diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json
index 91247105d..b13404e91 100644
--- a/app/javascript/dashboard/i18n/locale/en/conversation.json
+++ b/app/javascript/dashboard/i18n/locale/en/conversation.json
@@ -20,6 +20,8 @@
"LOADING_CONVERSATIONS": "Loading Conversations",
"CANNOT_REPLY": "You cannot reply due to",
"24_HOURS_WINDOW": "24 hour message window restriction",
+ "TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
+ "TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
"LAST_INCOMING_TWEET": "You are replying to the last incoming tweet",
"REPLYING_TO": "You are replying to:",
"REMOVE_SELECTION": "Remove Selection",
@@ -29,10 +31,14 @@
"HEADER": {
"RESOLVE_ACTION": "Resolve",
"REOPEN_ACTION": "Reopen",
+ "OPEN_ACTION": "Open",
"OPEN": "More",
"CLOSE": "Close",
"DETAILS": "details"
},
+ "RESOLVE_DROPDOWN": {
+ "OPEN_BOT": "Open with bot"
+ },
"FOOTER": {
"MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.",
"PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents"
@@ -52,6 +58,7 @@
"CHANGE_STATUS": "Conversation status changed",
"CHANGE_AGENT": "Conversation Assignee changed",
"CHANGE_TEAM": "Conversation team changed",
+ "FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
"SENT_BY": "Sent by:",
"ASSIGNMENT": {
"SELECT_AGENT": "Select Agent",
@@ -108,5 +115,4 @@
"PLACEHOLDER": "None"
}
}
-
}
diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
index e63a40656..a1184f5db 100644
--- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
@@ -5,7 +5,8 @@
"LIST": {
"404": "There are no inboxes attached to this account."
},
- "CREATE_FLOW": [{
+ "CREATE_FLOW": [
+ {
"title": "Choose Channel",
"route": "settings_inbox_new",
"body": "Choose the provider you want to integrate with Chatwoot."
@@ -193,6 +194,7 @@
"TITLE": "Your Inbox is ready!",
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting ",
"BUTTON_TEXT": "Take me there",
+ "MORE_SETTINGS": "More settings",
"WEBSITE_SUCCESS": "You have successfully finished creating a website channel. Copy the code shown below and paste it on your website. Next time a customer use the live chat, the conversation will automatically appear on your inbox."
},
"REAUTH": "Reauthorize",
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index fd915fb0c..314bbbbda 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -3,6 +3,8 @@
"LINK": "Profile Settings",
"TITLE": "Profile Settings",
"BTN_TEXT": "Update Profile",
+ "UPDATE_SUCCESS": "Your profile has been updated successfully",
+ "PASSWORD_UPDATE_SUCCESS": "Your password has been changed successfully",
"AFTER_EMAIL_CHANGED": "Your profile has been updated successfully, please login again as your login credentials are changed",
"FORM": {
"AVATAR": "Profile Image",
@@ -16,7 +18,8 @@
},
"PASSWORD_SECTION": {
"TITLE": "Password",
- "NOTE": "Updating your password would reset your logins in multiple devices."
+ "NOTE": "Updating your password would reset your logins in multiple devices.",
+ "BTN_TEXT": "Change password"
},
"ACCESS_TOKEN": {
"TITLE": "Access Token",
@@ -64,11 +67,7 @@
},
"AVAILABILITY": {
"LABEL": "Availability",
- "STATUSES_LIST": [
- "Online",
- "Busy",
- "Offline"
- ]
+ "STATUSES_LIST": ["Online", "Busy", "Offline"]
},
"EMAIL": {
"LABEL": "Your email address",
@@ -147,6 +146,5 @@
},
"SUBMIT": "Submit"
}
-
}
}
diff --git a/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json
index c3137e769..fdae3d169 100644
--- a/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/id/inboxMgmt.json
@@ -118,7 +118,7 @@
"TITLE": "URL Callback",
"SUBTITLE": "Anda harus mengkonfigurasi pesan URL callback di Twilio dengan URL yang disebutkan di sini."
},
- "SUBMIT_BUTTON": "Buat Channel Twillio",
+ "SUBMIT_BUTTON": "Buat Channel Twilio",
"API": {
"ERROR_MESSAGE": "Kami tidak dapat mengautentikasi kredensial Twilio, harap coba lagi"
}
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue
index 45cc635ad..32082532e 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue
@@ -3,7 +3,7 @@
-
+
{},
@@ -135,8 +139,9 @@ export default {
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
- agents: 'agents/getVerifiedAgents',
teams: 'teams/getTeams',
+ getAgents: 'inboxMembers/getMembersByInbox',
+ uiFlags: 'inboxMembers/getUIFlags',
}),
currentConversationMetaData() {
return this.$store.getters[
@@ -182,7 +187,7 @@ export default {
return '';
}
const countryFlag = countryCode ? flag(countryCode) : '🌎';
- return `${countryFlag} ${cityAndCountry}`;
+ return `${cityAndCountry} ${countryFlag}`;
},
platformName() {
const {
@@ -201,7 +206,7 @@ export default {
return this.$store.getters['contacts/getContact'](this.contactId);
},
agentsList() {
- return [{ id: 0, name: 'None' }, ...this.agents];
+ return [{ id: 0, name: 'None' }, ...this.getAgents(this.inboxId)];
},
teamsList() {
return [{ id: 0, name: 'None' }, ...this.teams];
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
index a1beaa13e..0fe9a1a4d 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
@@ -35,12 +35,26 @@
-
+
+
+
+ {{ $t('CONTACT_FORM.FORM.PHONE_NUMBER.HELP') }}
+
+
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
+
+
+ {{ $t('CONTACT_PANEL.NEW_MESSAGE') }}
+
+
+ {{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
+
+
+
@@ -70,6 +93,7 @@ import ContactInfoRow from './ContactInfoRow';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import SocialIcons from './SocialIcons';
import EditContact from './EditContact';
+import NewConversation from './NewConversation';
export default {
components: {
@@ -77,6 +101,7 @@ export default {
EditContact,
Thumbnail,
SocialIcons,
+ NewConversation,
},
props: {
contact: {
@@ -87,10 +112,15 @@ export default {
type: String,
default: '',
},
+ showNewMessage: {
+ type: Boolean,
+ default: false,
+ },
},
data() {
return {
showEditModal: false,
+ showConversationModal: false,
};
},
computed: {
@@ -110,6 +140,9 @@ export default {
toggleEditModal() {
this.showEditModal = !this.showEditModal;
},
+ toggleConversationModal() {
+ this.showConversationModal = !this.showConversationModal;
+ },
},
};
@@ -119,7 +152,7 @@ export default {
@import '~dashboard/assets/scss/mixins';
.contact--profile {
align-items: flex-start;
- padding: var(--space-normal) var(--space-normal) var(--space-large);
+ padding: var(--space-normal) var(--space-normal);
.user-thumbnail-box {
margin-right: $space-normal;
@@ -163,10 +196,21 @@ export default {
font-size: $font-weight-normal;
}
}
+.contact-actions {
+ margin: var(--space-small) 0;
+}
+.button.edit-contact {
+ margin-left: var(--space-two);
+ padding-left: var(--space-micro);
+}
-.edit-contact {
- padding: 0 var(--space-slab);
- margin-left: var(--space-slab);
- margin-top: var(--space-smaller);
+.button.new-message {
+ margin-right: var(--space-small);
+}
+
+.contact-actions {
+ display: flex;
+ align-items: center;
+ width: 100%;
}
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
new file mode 100644
index 000000000..75e333a73
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ConversationForm.vue
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue
new file mode 100644
index 000000000..02e9c4f58
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/NewConversation.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue b/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue
index fd97c371d..c6960c9e2 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/search/PopOverSearch.vue
@@ -1,6 +1,7 @@
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue
index 630bc0652..80b37079c 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue
@@ -29,15 +29,26 @@
>
-
- {{ $t('INBOX_MGMT.FINISH.BUTTON_TEXT') }}
-
+
@@ -90,4 +101,13 @@ export default {
margin: $space-normal auto;
max-width: 70%;
}
+
+.footer {
+ display: flex;
+ justify-content: center;
+}
+
+.settings-button {
+ margin-right: var(--space-small);
+}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue
index 73b4ade40..90d6f7012 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/profile/Index.vue
@@ -1,8 +1,8 @@
-
-