+
{{ title }}
+ {{ thHeader }} + | + + +
---|
{{ $t('MACROS.EDITOR.VISIBILITY.LABEL') }}
++ {{ $t('MACROS.ORDER_INFO') }} +
++ {{ resultsTitle }} +
+-
+
- + + {{ article.title }} + + +
- {{ multiselectorTitle }} -
++ {{ multiselectorTitle }} +
+ {
- if (
- (isUserEmailAvailable && field.name === 'emailAddress') ||
- (isUserPhoneNumberAvailable && field.name === 'phoneNumber')
- ) {
+ if (isUserEmailAvailable && field.name === 'emailAddress') {
+ return false;
+ }
+ if (isUserPhoneNumberAvailable && field.name === 'phoneNumber') {
+ return false;
+ }
+ if (isUserNameAvailable && field.name === 'fullName') {
return false;
}
return true;
@@ -236,6 +245,7 @@ export default {
text: null,
select: null,
number: null,
+ checkbox: false,
};
const validationKeys = Object.keys(validations);
const validation = 'bail|required';
diff --git a/app/javascript/widget/components/TeamAvailability.vue b/app/javascript/widget/components/TeamAvailability.vue
index 1f4c5d046..a3a6b0c02 100644
--- a/app/javascript/widget/components/TeamAvailability.vue
+++ b/app/javascript/widget/components/TeamAvailability.vue
@@ -13,7 +13,7 @@
}}
- {{ replyWaitMeessage }}
+ {{ replyWaitMessage }}
@@ -75,7 +75,7 @@ export default {
}
return anyAgentOnline;
},
- replyWaitMeessage() {
+ replyWaitMessage() {
const { workingHoursEnabled } = this.channelConfig;
if (this.isOnline) {
diff --git a/app/javascript/widget/components/UnreadMessageList.vue b/app/javascript/widget/components/UnreadMessageList.vue
index 19afe49d1..6683c585e 100644
--- a/app/javascript/widget/components/UnreadMessageList.vue
+++ b/app/javascript/widget/components/UnreadMessageList.vue
@@ -107,13 +107,12 @@ export default {
.clear-button {
background: transparent;
color: $color-woot;
- padding: 0;
border: 0;
font-weight: $font-weight-bold;
font-size: $font-size-medium;
transition: all 0.3s var(--ease-in-cubic);
margin-left: $space-smaller;
- padding-right: $space-one;
+ padding: 0 $space-one 0 0;
&:hover {
transform: translateX($space-smaller);
diff --git a/app/javascript/widget/helpers/IframeEventHelper.js b/app/javascript/widget/helpers/IframeEventHelper.js
index 953802df8..40c0e1f1c 100644
--- a/app/javascript/widget/helpers/IframeEventHelper.js
+++ b/app/javascript/widget/helpers/IframeEventHelper.js
@@ -10,7 +10,7 @@ export const loadedEventConfig = () => {
export const getExtraSpaceToScroll = () => {
// This function calculates the extra space needed for the view to
- // accomodate the height of close button + height of
+ // accommodate the height of close button + height of
// read messages button. So that scrollbar won't appear
const unreadMessageWrap = document.querySelector('.unread-messages');
const unreadCloseWrap = document.querySelector('.close-unread-wrap');
diff --git a/app/javascript/widget/helpers/specs/urlParamsHelper.spec.js b/app/javascript/widget/helpers/specs/urlParamsHelper.spec.js
index 826c35e99..87588616b 100644
--- a/app/javascript/widget/helpers/specs/urlParamsHelper.spec.js
+++ b/app/javascript/widget/helpers/specs/urlParamsHelper.spec.js
@@ -31,7 +31,7 @@ describe('#getLocale', () => {
expect(getLocale('?test=1&cw_conv=2&website_token=3&locale=fr')).toEqual(
'fr'
);
- expect(getLocale('')).toEqual(undefined);
+ expect(getLocale('')).toEqual(null);
});
});
diff --git a/app/javascript/widget/helpers/urlParamsHelper.js b/app/javascript/widget/helpers/urlParamsHelper.js
index ad7f896d1..1736c092e 100644
--- a/app/javascript/widget/helpers/urlParamsHelper.js
+++ b/app/javascript/widget/helpers/urlParamsHelper.js
@@ -1,22 +1,13 @@
export const buildSearchParamsWithLocale = search => {
const locale = window.WOOT_WIDGET.$root.$i18n.locale;
- if (search) {
- search = `${search}&locale=${locale}`;
- } else {
- search = `?locale=${locale}`;
- }
- return search;
+ const params = new URLSearchParams(search);
+ params.append('locale', locale);
+
+ return `?${params}`;
};
export const getLocale = (search = '') => {
- const searchParamKeyValuePairs = search.split('&');
- return searchParamKeyValuePairs.reduce((acc, keyValuePair) => {
- const [key, value] = keyValuePair.split('=');
- if (key === 'locale') {
- return value;
- }
- return acc;
- }, undefined);
+ return new URLSearchParams(search).get('locale');
};
export const buildPopoutURL = ({
@@ -25,5 +16,10 @@ export const buildPopoutURL = ({
websiteToken,
locale,
}) => {
- return `${origin}/widget?cw_conversation=${conversationCookie}&website_token=${websiteToken}&locale=${locale}`;
+ const popoutUrl = new URL('/widget', origin);
+ popoutUrl.searchParams.append('cw_conversation', conversationCookie);
+ popoutUrl.searchParams.append('website_token', websiteToken);
+ popoutUrl.searchParams.append('locale', locale);
+
+ return popoutUrl.toString();
};
diff --git a/app/javascript/widget/helpers/utils.js b/app/javascript/widget/helpers/utils.js
index 1a4b2c1d6..a4d5dafa3 100755
--- a/app/javascript/widget/helpers/utils.js
+++ b/app/javascript/widget/helpers/utils.js
@@ -3,13 +3,6 @@ import { WOOT_PREFIX } from './constants';
export const isEmptyObject = obj =>
Object.keys(obj).length === 0 && obj.constructor === Object;
-export const arrayToHashById = array =>
- array.reduce((map, obj) => {
- const newMap = map;
- newMap[obj.id] = obj;
- return newMap;
- }, {});
-
export const sendMessage = msg => {
window.parent.postMessage(
`chatwoot-widget:${JSON.stringify({ ...msg })}`,
@@ -22,9 +15,7 @@ export const IFrameHelper = {
sendMessage,
isAValidEvent: e => {
const isDataAString = typeof e.data === 'string';
- const isAValidWootEvent =
- isDataAString && e.data.indexOf(WOOT_PREFIX) === 0;
- return isAValidWootEvent;
+ return isDataAString && e.data.indexOf(WOOT_PREFIX) === 0;
},
getMessage: e => JSON.parse(e.data.replace(WOOT_PREFIX, '')),
};
diff --git a/app/javascript/widget/store/modules/conversation/getters.js b/app/javascript/widget/store/modules/conversation/getters.js
index 9d1c067f4..74e582348 100644
--- a/app/javascript/widget/store/modules/conversation/getters.js
+++ b/app/javascript/widget/store/modules/conversation/getters.js
@@ -32,7 +32,7 @@ export const getters = {
},
getUnreadMessageCount: _state => {
const { userLastSeenAt } = _state.meta;
- const count = Object.values(_state.conversations).filter(chat => {
+ return Object.values(_state.conversations).filter(chat => {
const { created_at: createdAt, message_type: messageType } = chat;
const isOutGoing = messageType === MESSAGE_TYPE.OUTGOING;
const hasNotSeen = userLastSeenAt
@@ -40,7 +40,6 @@ export const getters = {
: true;
return hasNotSeen && isOutGoing;
}).length;
- return count;
},
getUnreadTextMessages: (_state, _getters) => {
const unreadCount = _getters.getUnreadMessageCount;
@@ -50,7 +49,6 @@ export const getters = {
return messageType === MESSAGE_TYPE.OUTGOING;
});
const maxUnreadCount = Math.min(unreadCount, 3);
- const allUnreadMessages = unreadAgentMessages.splice(-maxUnreadCount);
- return allUnreadMessages;
+ return unreadAgentMessages.splice(-maxUnreadCount);
},
};
diff --git a/app/javascript/widget/store/modules/conversation/helpers.js b/app/javascript/widget/store/modules/conversation/helpers.js
index 44e2a6729..2ebac5242 100644
--- a/app/javascript/widget/store/modules/conversation/helpers.js
+++ b/app/javascript/widget/store/modules/conversation/helpers.js
@@ -29,7 +29,7 @@ const shouldShowAvatar = (message, nextMessage) => {
export const groupConversationBySender = conversationsForADate =>
conversationsForADate.map((message, index) => {
- let showAvatar = false;
+ let showAvatar;
const isLastMessage = index === conversationsForADate.length - 1;
if (isASubmittedFormMessage(message)) {
showAvatar = false;
diff --git a/app/javascript/widget/store/modules/conversation/mutations.js b/app/javascript/widget/store/modules/conversation/mutations.js
index f47971f07..ca6dafada 100644
--- a/app/javascript/widget/store/modules/conversation/mutations.js
+++ b/app/javascript/widget/store/modules/conversation/mutations.js
@@ -88,8 +88,7 @@ export const mutations = {
},
toggleAgentTypingStatus($state, { status }) {
- const isTyping = status === 'on';
- $state.uiFlags.isAgentTyping = isTyping;
+ $state.uiFlags.isAgentTyping = status === 'on';
},
setMetaUserLastSeenAt($state, lastSeen) {
diff --git a/app/javascript/widget/store/modules/conversationLabels.js b/app/javascript/widget/store/modules/conversationLabels.js
index 3fbcd230d..3ae600082 100644
--- a/app/javascript/widget/store/modules/conversationLabels.js
+++ b/app/javascript/widget/store/modules/conversationLabels.js
@@ -9,14 +9,14 @@ export const actions = {
try {
await conversationLabels.create(label);
} catch (error) {
- // Ingore error
+ // Ignore error
}
},
destroy: async (_, label) => {
try {
await conversationLabels.destroy(label);
} catch (error) {
- // Ingore error
+ // Ignore error
}
},
};
diff --git a/app/jobs/webhooks/instagram_events_job.rb b/app/jobs/webhooks/instagram_events_job.rb
index f0801fbc8..0f78c6277 100644
--- a/app/jobs/webhooks/instagram_events_job.rb
+++ b/app/jobs/webhooks/instagram_events_job.rb
@@ -13,6 +13,7 @@ class Webhooks::InstagramEventsJob < ApplicationJob
@entries = entries
@entries.each do |entry|
+ entry = entry.with_indifferent_access
entry[:messaging].each do |messaging|
send(@event_name, messaging) if event_name(messaging)
end
diff --git a/app/listeners/action_cable_listener.rb b/app/listeners/action_cable_listener.rb
index 3d3bbdf83..e7c025600 100644
--- a/app/listeners/action_cable_listener.rb
+++ b/app/listeners/action_cable_listener.rb
@@ -52,6 +52,13 @@ class ActionCableListener < BaseListener
broadcast(account, tokens, CONVERSATION_STATUS_CHANGED, conversation.push_event_data)
end
+ def conversation_updated(event)
+ conversation, account = extract_conversation_and_account(event)
+ tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox)
+
+ broadcast(account, tokens, CONVERSATION_UPDATED, conversation.push_event_data)
+ end
+
def conversation_typing_on(event)
conversation = event.data[:conversation]
account = conversation.account
diff --git a/app/listeners/webhook_listener.rb b/app/listeners/webhook_listener.rb
index 11a86e639..81d2c6b0a 100644
--- a/app/listeners/webhook_listener.rb
+++ b/app/listeners/webhook_listener.rb
@@ -54,7 +54,7 @@ class WebhookListener < BaseListener
private
def deliver_account_webhooks(payload, inbox)
- inbox.account.webhooks.account.each do |webhook|
+ inbox.account.webhooks.account_type.each do |webhook|
next unless webhook.subscriptions.include?(payload[:event])
WebhookJob.perform_later(webhook.url, payload)
diff --git a/app/mailboxes/mailbox_helper.rb b/app/mailboxes/mailbox_helper.rb
index 1519343ca..216d5c2c3 100644
--- a/app/mailboxes/mailbox_helper.rb
+++ b/app/mailboxes/mailbox_helper.rb
@@ -34,7 +34,7 @@ module MailboxHelper
end
def create_contact
- @contact_inbox = ::ContactBuilder.new(
+ @contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: processed_mail.original_sender,
inbox: @inbox,
contact_attributes: {
diff --git a/app/models/channel/facebook_page.rb b/app/models/channel/facebook_page.rb
index 2153708e2..bba45f326 100644
--- a/app/models/channel/facebook_page.rb
+++ b/app/models/channel/facebook_page.rb
@@ -37,16 +37,11 @@ class Channel::FacebookPage < ApplicationRecord
end
def create_contact_inbox(instagram_id, name)
- ActiveRecord::Base.transaction do
- contact = inbox.account.contacts.create!(name: name)
- ::ContactInbox.create!(
- contact_id: contact.id,
- inbox_id: inbox.id,
- source_id: instagram_id
- )
- rescue StandardError => e
- Rails.logger.error e
- end
+ @contact_inbox = ::ContactInboxWithContactBuilder.new({
+ source_id: instagram_id,
+ inbox: inbox,
+ contact_attributes: { name: name }
+ }).perform
end
def subscribe
diff --git a/app/models/channel/twitter_profile.rb b/app/models/channel/twitter_profile.rb
index 4f6fa7ba1..d0f765e9f 100644
--- a/app/models/channel/twitter_profile.rb
+++ b/app/models/channel/twitter_profile.rb
@@ -32,16 +32,11 @@ class Channel::TwitterProfile < ApplicationRecord
end
def create_contact_inbox(profile_id, name, additional_attributes)
- ActiveRecord::Base.transaction do
- contact = inbox.account.contacts.create!(additional_attributes: additional_attributes, name: name)
- ::ContactInbox.create!(
- contact_id: contact.id,
- inbox_id: inbox.id,
- source_id: profile_id
- )
- rescue StandardError => e
- Rails.logger.error e
- end
+ ::ContactInboxWithContactBuilder.new({
+ source_id: profile_id,
+ inbox: inbox,
+ contact_attributes: { name: name, additional_attributes: additional_attributes }
+ }).perform
end
def twitter_client
diff --git a/app/models/channel/web_widget.rb b/app/models/channel/web_widget.rb
index b85443633..59d392892 100644
--- a/app/models/channel/web_widget.rb
+++ b/app/models/channel/web_widget.rb
@@ -98,19 +98,9 @@ class Channel::WebWidget < ApplicationRecord
end
def create_contact_inbox(additional_attributes = {})
- ActiveRecord::Base.transaction do
- contact = inbox.account.contacts.create!(
- name: ::Haikunator.haikunate(1000),
- additional_attributes: additional_attributes
- )
- contact_inbox = ::ContactInbox.create!(
- contact_id: contact.id,
- inbox_id: inbox.id,
- source_id: SecureRandom.uuid
- )
- contact_inbox
- rescue StandardError => e
- Rails.logger.error e
- end
+ ::ContactInboxWithContactBuilder.new({
+ inbox: inbox,
+ contact_attributes: { additional_attributes: additional_attributes }
+ }).perform
end
end
diff --git a/app/models/message.rb b/app/models/message.rb
index 2930786f9..a3f63dbe9 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -116,7 +116,7 @@ class Message < ApplicationRecord
end
def webhook_data
- {
+ data = {
account: account.webhook_data,
additional_attributes: additional_attributes,
content_attributes: content_attributes,
@@ -131,6 +131,8 @@ class Message < ApplicationRecord
sender: sender.try(:webhook_data),
source_id: source_id
}
+ data.merge!(attachments: attachments.map(&:push_event_data)) if attachments.present?
+ data
end
def content
diff --git a/app/models/user.rb b/app/models/user.rb
index 6ed95ddb3..743471049 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -96,7 +96,7 @@ class User < ApplicationRecord
class_name: :PortalMember,
dependent: :destroy_async
has_many :portals,
- through: :portals_members,
+ through: :portal_members,
class_name: :Portal,
dependent: :nullify,
source: :portal
diff --git a/app/models/webhook.rb b/app/models/webhook.rb
index fe97fe583..5b0095093 100644
--- a/app/models/webhook.rb
+++ b/app/models/webhook.rb
@@ -5,7 +5,7 @@
# id :bigint not null, primary key
# subscriptions :jsonb
# url :string
-# webhook_type :integer default("account")
+# webhook_type :integer default("account_type")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer
@@ -23,7 +23,7 @@ class Webhook < ApplicationRecord
validates :account_id, presence: true
validates :url, uniqueness: { scope: [:account_id] }, format: URI::DEFAULT_PARSER.make_regexp(%w[http https])
validate :validate_webhook_subscriptions
- enum webhook_type: { account: 0, inbox: 1 }
+ enum webhook_type: { account_type: 0, inbox_type: 1 }
ALLOWED_WEBHOOK_EVENTS = %w[conversation_status_changed conversation_updated conversation_created message_created message_updated
webwidget_triggered].freeze
diff --git a/app/services/instagram/message_text.rb b/app/services/instagram/message_text.rb
index abcbe2d60..6ce8ec02b 100644
--- a/app/services/instagram/message_text.rb
+++ b/app/services/instagram/message_text.rb
@@ -53,8 +53,10 @@ class Instagram::MessageText < Instagram::WebhooksBaseService
@messaging[:message][:is_deleted].present?
end
+ # if contact was present before find out contact_inbox to create message
def contacts_first_message?(ig_scope_id)
- @inbox.contact_inboxes.where(source_id: ig_scope_id).empty? && @inbox.channel.instagram_id.present?
+ @contact_inbox = @inbox.contact_inboxes.where(source_id: ig_scope_id).last
+ @contact_inbox.blank? && @inbox.channel.instagram_id.present?
end
def sent_via_test_webhook?
diff --git a/app/services/line/incoming_message_service.rb b/app/services/line/incoming_message_service.rb
index 535c03dc7..48a52eb8f 100644
--- a/app/services/line/incoming_message_service.rb
+++ b/app/services/line/incoming_message_service.rb
@@ -81,7 +81,7 @@ class Line::IncomingMessageService
end
def set_contact
- contact_inbox = ::ContactBuilder.new(
+ contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: line_contact_info['userId'],
inbox: inbox,
contact_attributes: contact_attributes
diff --git a/app/services/sms/incoming_message_service.rb b/app/services/sms/incoming_message_service.rb
index 7ee6e3e63..7aa22b19e 100644
--- a/app/services/sms/incoming_message_service.rb
+++ b/app/services/sms/incoming_message_service.rb
@@ -37,7 +37,7 @@ class Sms::IncomingMessageService
end
def set_contact
- contact_inbox = ::ContactBuilder.new(
+ contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: params[:from],
inbox: @inbox,
contact_attributes: contact_attributes
diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb
index a26bda14e..e8ab8a72c 100644
--- a/app/services/telegram/incoming_message_service.rb
+++ b/app/services/telegram/incoming_message_service.rb
@@ -31,7 +31,7 @@ class Telegram::IncomingMessageService
end
def set_contact
- contact_inbox = ::ContactBuilder.new(
+ contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: params[:message][:from][:id],
inbox: inbox,
contact_attributes: contact_attributes
diff --git a/app/services/twilio/incoming_message_service.rb b/app/services/twilio/incoming_message_service.rb
index 50c77111c..4473131df 100644
--- a/app/services/twilio/incoming_message_service.rb
+++ b/app/services/twilio/incoming_message_service.rb
@@ -47,7 +47,7 @@ class Twilio::IncomingMessageService
end
def set_contact
- contact_inbox = ::ContactBuilder.new(
+ contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: params[:From],
inbox: inbox,
contact_attributes: contact_attributes
diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb
index e55d5fd26..da08e02d6 100644
--- a/app/services/whatsapp/incoming_message_base_service.rb
+++ b/app/services/whatsapp/incoming_message_base_service.rb
@@ -12,7 +12,7 @@ class Whatsapp::IncomingMessageBaseService
set_conversation
- return if @processed_params[:messages].blank?
+ return if @processed_params[:messages].blank? || unprocessable_message_type?
@message = @conversation.messages.build(
content: message_content(@processed_params[:messages].first),
@@ -48,7 +48,7 @@ class Whatsapp::IncomingMessageBaseService
contact_params = @processed_params[:contacts]&.first
return if contact_params.blank?
- contact_inbox = ::ContactBuilder.new(
+ contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: contact_params[:wa_id],
inbox: inbox,
contact_attributes: { name: contact_params.dig(:profile, :name), phone_number: "+#{@processed_params[:messages].first[:from]}" }
@@ -86,6 +86,10 @@ class Whatsapp::IncomingMessageBaseService
@processed_params[:messages].first[:type]
end
+ def unprocessable_message_type?
+ %w[reaction contacts].include?(message_type)
+ end
+
def attach_files
return if %w[text button interactive].include?(message_type)
diff --git a/app/views/api/v1/widget/contacts/show.json.jbuilder b/app/views/api/v1/widget/contacts/show.json.jbuilder
index d6228cbfe..2e7a38277 100644
--- a/app/views/api/v1/widget/contacts/show.json.jbuilder
+++ b/app/views/api/v1/widget/contacts/show.json.jbuilder
@@ -2,3 +2,4 @@ json.id @contact.id
json.name @contact.name
json.email @contact.email
json.phone_number @contact.phone_number
+json.identifier @contact.identifier
diff --git a/app/views/fields/account_features_field/_form.html.erb b/app/views/fields/account_features_field/_form.html.erb
index 823a043a6..c067e81c1 100644
--- a/app/views/fields/account_features_field/_form.html.erb
+++ b/app/views/fields/account_features_field/_form.html.erb
@@ -4,5 +4,6 @@
<% field.data.each do |key,val| %>
<%= key %>: <%= check_box "enabled_features", "feature_#{key}", { checked: val }, true, false %>
+
<% end %>
diff --git a/app/views/layouts/portal.html.erb b/app/views/layouts/portal.html.erb
index 533c32d10..071fde9ee 100644
--- a/app/views/layouts/portal.html.erb
+++ b/app/views/layouts/portal.html.erb
@@ -17,6 +17,7 @@ By default, it renders:
+
<%= javascript_pack_tag 'portal' %>
<%= stylesheet_pack_tag 'portal' %>
<%= csrf_meta_tags %>
@@ -35,4 +36,16 @@ By default, it renders:
+
<% end %>