{{ localeName(locale.code) }}
@@ -90,6 +90,10 @@ export default {
type: String,
default: '',
},
+ activeLocale: {
+ type: String,
+ default: '',
+ },
},
data() {
return {
@@ -129,7 +133,7 @@ export default {
},
isLocaleActive(code, slug) {
const isPortalActive = this.portal.slug === slug;
- const isLocaleActive = this.portal?.meta?.default_locale === code;
+ const isLocaleActive = this.activeLocale === code;
return isPortalActive && isLocaleActive;
},
isLocaleDefault(code) {
@@ -151,6 +155,7 @@ export default {
&.active {
border: 1px solid var(--w-400);
+ background: var(---25);
}
.actions-container {
@@ -177,15 +182,13 @@ export default {
.portal-count {
font-size: var(--font-size-mini);
margin-bottom: 0;
- color: var(--s-500);
+ color: var(--s-600);
}
}
.portal-locales {
- .locale-title {
- color: var(--s-600);
- font-size: var(--font-size-default);
- font-weight: var(--font-weight-medium);
+ .locale-name {
+ margin-bottom: var(--space-micro);
}
.locale-content {
@@ -204,6 +207,7 @@ export default {
.locale__radio {
width: var(--space-large);
margin-top: var(--space-tiny);
+ color: var(--g-600);
}
.add-locale-wrap {
@@ -227,7 +231,7 @@ export default {
.locale-meta {
display: flex;
- color: var(--s-500);
+ color: var(--s-600);
font-size: var(--font-size-small);
text-align: left;
line-height: var(--space-normal);
diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue
index e6dc1f427..d2e14b6a8 100644
--- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue
+++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue
@@ -69,7 +69,7 @@ export default {
},
portalLink() {
const slug = this.$route.params.portalSlug;
- return `/public/api/v1/portals/${slug}`;
+ return `/hc/${slug}`;
},
},
methods: {
@@ -89,6 +89,8 @@ export default {
diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue
index c91702da9..656c8fecf 100644
--- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue
+++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue
@@ -86,7 +86,7 @@ export default {
},
portalLink() {
const slug = this.$route.params.portalSlug;
- return `/public/api/v1/portals/${slug}`;
+ return `/hc/${slug}`;
},
},
mounted() {
diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/AddCategory.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/AddCategory.vue
index 0fb6bcd2b..95476c0df 100644
--- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/AddCategory.vue
+++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/categories/AddCategory.vue
@@ -86,6 +86,10 @@ export default {
type: String,
default: '',
},
+ portalSlug: {
+ type: String,
+ default: '',
+ },
},
data() {
return {
@@ -105,7 +109,9 @@ export default {
},
computed: {
selectedPortalSlug() {
- return this.$route.params.portalSlug;
+ return this.$route.params.portalSlug
+ ? this.$route.params.portalSlug
+ : this.portalSlug;
},
nameError() {
if (this.$v.name.$error) {
diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue
index 1e0023e6c..bae35b5af 100644
--- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue
+++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/EditPortalLocales.vue
@@ -140,13 +140,13 @@ export default {
width: 100%;
background: var(--white);
height: 100%;
- padding: 0 var(--space-medium);
+ padding: 0 0 0 var(--space-normal);
.button-container {
display: flex;
justify-content: flex-end;
}
.locale-container {
- margin-top: var(--space-large);
+ margin-top: var(--space-normal);
}
}
diff --git a/app/javascript/packs/portal.js b/app/javascript/packs/portal.js
new file mode 100644
index 000000000..2f7736deb
--- /dev/null
+++ b/app/javascript/packs/portal.js
@@ -0,0 +1,16 @@
+// This file is automatically compiled by Webpack, along with any other files
+// present in this directory. You're encouraged to place your actual application logic in
+// a relevant structure within app/javascript and only use these pack files to reference
+// that code so that it will be compiled.
+
+import Rails from '@rails/ujs';
+import Turbolinks from 'turbolinks';
+
+import { navigateToLocalePage } from '../portal/portalHelpers';
+
+import '../portal/application.scss';
+
+Rails.start();
+Turbolinks.start();
+
+document.addEventListener('DOMContentLoaded', navigateToLocalePage);
diff --git a/app/javascript/portal/application.scss b/app/javascript/portal/application.scss
new file mode 100644
index 000000000..8e744f28d
--- /dev/null
+++ b/app/javascript/portal/application.scss
@@ -0,0 +1,78 @@
+@import 'tailwindcss/base';
+@import 'tailwindcss/components';
+@import 'tailwindcss/utilities';
+
+@import 'widget/assets/scss/reset';
+@import 'widget/assets/scss/variables';
+@import 'widget/assets/scss/buttons';
+@import 'widget/assets/scss/mixins';
+@import 'widget/assets/scss/forms';
+@import 'shared/assets/fonts/widget_fonts';
+
+html,
+body {
+ font-family: $font-family;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ height: 100%;
+}
+
+.woot-survey-wrap {
+ height: 100%;
+}
+
+.blog-content {
+ @apply text-lg;
+ @apply font-sans;
+ @apply text-slate-800;
+ @apply leading-normal;
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ @apply font-sans leading-relaxed font-extrabold text-slate-900;
+ @apply mb-4;
+ @apply mt-8;
+ }
+
+ h1 {
+ @apply text-5xl leading-normal;
+ }
+
+ h2 {
+ @apply text-4xl leading-normal;
+ }
+
+ h3 {
+ @apply text-3xl leading-normal;
+ }
+
+ h4 {
+ @apply text-2xl leading-normal;
+ }
+
+ p {
+ @apply text-lg;
+ @apply font-sans;
+ @apply text-slate-800;
+ @apply leading-relaxed;
+ @apply mb-4;
+ }
+
+ ul {
+ @apply list-disc;
+ @apply pl-8;
+ @apply ml-4;
+ }
+
+ li {
+ @apply text-lg;
+ @apply font-sans;
+ @apply text-slate-800;
+ @apply leading-relaxed;
+ @apply mb-2;
+ }
+}
diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js
new file mode 100644
index 000000000..d31d1b6ba
--- /dev/null
+++ b/app/javascript/portal/portalHelpers.js
@@ -0,0 +1,8 @@
+export const navigateToLocalePage = () => {
+ const allLocaleSwitcher = document.querySelector('.locale-switcher');
+
+ const { portalSlug } = allLocaleSwitcher.dataset;
+ allLocaleSwitcher.addEventListener('change', event => {
+ window.location = `/hc/${portalSlug}/${event.target.value}/`;
+ });
+};
diff --git a/app/javascript/portal/specs/portal.spec.js b/app/javascript/portal/specs/portal.spec.js
new file mode 100644
index 000000000..31dc06890
--- /dev/null
+++ b/app/javascript/portal/specs/portal.spec.js
@@ -0,0 +1,23 @@
+import { navigateToLocalePage } from '../portalHelpers';
+
+describe('#navigateToLocalePage', () => {
+ it('returns correct cookie name', () => {
+ const elemDiv = document.createElement('div');
+ elemDiv.classList.add('locale-switcher');
+ document.body.appendChild(elemDiv);
+
+ const allLocaleSwitcher = document.querySelector('.locale-switcher');
+
+ allLocaleSwitcher.addEventListener = jest
+ .fn()
+ .mockImplementationOnce((event, callback) => {
+ callback({ target: { value: 1 } });
+ });
+
+ navigateToLocalePage();
+ expect(allLocaleSwitcher.addEventListener).toBeCalledWith(
+ 'change',
+ expect.any(Function)
+ );
+ });
+});
diff --git a/app/javascript/shared/helpers/KeyboardHelpers.js b/app/javascript/shared/helpers/KeyboardHelpers.js
index 079b5ab62..a1a746e0d 100644
--- a/app/javascript/shared/helpers/KeyboardHelpers.js
+++ b/app/javascript/shared/helpers/KeyboardHelpers.js
@@ -42,10 +42,6 @@ export const hasPressedAltAndNKey = e => {
return e.altKey && e.keyCode === 78;
};
-export const hasPressedAltAndWKey = e => {
- return e.altKey && e.keyCode === 87;
-};
-
export const hasPressedAltAndAKey = e => {
return e.altKey && e.keyCode === 65;
};
diff --git a/app/javascript/shared/helpers/emoji.js b/app/javascript/shared/helpers/emoji.js
index 371472623..5323a14ce 100644
--- a/app/javascript/shared/helpers/emoji.js
+++ b/app/javascript/shared/helpers/emoji.js
@@ -34,11 +34,14 @@ export const hasEmojiSupport = () => {
};
export const removeEmoji = text => {
- return text
- .replace(
- /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
- ''
- )
- .replace(/\s+/g, ' ')
- .trim();
+ if (text) {
+ return text
+ .replace(
+ /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
+ ''
+ )
+ .replace(/\s+/g, ' ')
+ .trim();
+ }
+ return '';
};
diff --git a/app/jobs/conversations/activity_message_job.rb b/app/jobs/conversations/activity_message_job.rb
index a0982f472..54f631ee6 100644
--- a/app/jobs/conversations/activity_message_job.rb
+++ b/app/jobs/conversations/activity_message_job.rb
@@ -2,6 +2,6 @@ class Conversations::ActivityMessageJob < ApplicationJob
queue_as :default
def perform(conversation, message_params)
- conversation.messages.create(message_params)
+ conversation.messages.create!(message_params)
end
end
diff --git a/app/jobs/conversations/user_mention_job.rb b/app/jobs/conversations/user_mention_job.rb
index 1eed044a5..fe162792e 100644
--- a/app/jobs/conversations/user_mention_job.rb
+++ b/app/jobs/conversations/user_mention_job.rb
@@ -10,7 +10,7 @@ class Conversations::UserMentionJob < ApplicationJob
)
if mention.nil?
- Mention.create(
+ Mention.create!(
user_id: mentioned_user_id,
conversation_id: conversation_id,
mentioned_at: Time.zone.now,
diff --git a/app/listeners/reporting_event_listener.rb b/app/listeners/reporting_event_listener.rb
index abd28ad32..3a787e417 100644
--- a/app/listeners/reporting_event_listener.rb
+++ b/app/listeners/reporting_event_listener.rb
@@ -16,7 +16,7 @@ class ReportingEventListener < BaseListener
event_start_time: conversation.created_at,
event_end_time: conversation.updated_at
)
- reporting_event.save
+ reporting_event.save!
end
def first_reply_created(event)
@@ -39,6 +39,6 @@ class ReportingEventListener < BaseListener
# rubocop:disable Rails/SkipsModelValidations
conversation.update_columns(first_reply_created_at: message.created_at)
# rubocop:enable Rails/SkipsModelValidations
- reporting_event.save
+ reporting_event.save!
end
end
diff --git a/app/mailboxes/mailbox_helper.rb b/app/mailboxes/mailbox_helper.rb
index f0abc0eba..1519343ca 100644
--- a/app/mailboxes/mailbox_helper.rb
+++ b/app/mailboxes/mailbox_helper.rb
@@ -4,7 +4,7 @@ module MailboxHelper
def create_message
return if @conversation.messages.find_by(source_id: processed_mail.message_id).present?
- @message = @conversation.messages.create(
+ @message = @conversation.messages.create!(
account_id: @conversation.account_id,
sender: @conversation.contact,
content: mail_content&.truncate(150_000),
diff --git a/app/models/channel/facebook_page.rb b/app/models/channel/facebook_page.rb
index 3737279f8..2153708e2 100644
--- a/app/models/channel/facebook_page.rb
+++ b/app/models/channel/facebook_page.rb
@@ -39,7 +39,7 @@ class Channel::FacebookPage < ApplicationRecord
def create_contact_inbox(instagram_id, name)
ActiveRecord::Base.transaction do
contact = inbox.account.contacts.create!(name: name)
- ::ContactInbox.create(
+ ::ContactInbox.create!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: instagram_id
diff --git a/app/services/line/incoming_message_service.rb b/app/services/line/incoming_message_service.rb
index e3c85ef99..535c03dc7 100644
--- a/app/services/line/incoming_message_service.rb
+++ b/app/services/line/incoming_message_service.rb
@@ -30,7 +30,7 @@ class Line::IncomingMessageService
def message_created?(event)
return unless event_type_message?(event)
- @message = @conversation.messages.create(
+ @message = @conversation.messages.create!(
content: event['message']['text'],
account_id: @inbox.account_id,
inbox_id: @inbox.id,
diff --git a/app/services/sms/incoming_message_service.rb b/app/services/sms/incoming_message_service.rb
index ec9cf0a21..7ee6e3e63 100644
--- a/app/services/sms/incoming_message_service.rb
+++ b/app/services/sms/incoming_message_service.rb
@@ -6,7 +6,7 @@ class Sms::IncomingMessageService
def perform
set_contact
set_conversation
- @message = @conversation.messages.create(
+ @message = @conversation.messages.create!(
content: params[:text],
account_id: @inbox.account_id,
inbox_id: @inbox.id,
diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb
index ae26db899..d56e20e8d 100644
--- a/app/services/telegram/incoming_message_service.rb
+++ b/app/services/telegram/incoming_message_service.rb
@@ -12,7 +12,7 @@ class Telegram::IncomingMessageService
set_contact
update_contact_avatar
set_conversation
- @message = @conversation.messages.create(
+ @message = @conversation.messages.create!(
content: params[:message][:text].presence || params[:message][:caption],
account_id: @inbox.account_id,
inbox_id: @inbox.id,
diff --git a/app/services/twilio/incoming_message_service.rb b/app/services/twilio/incoming_message_service.rb
index 39d125eee..50c77111c 100644
--- a/app/services/twilio/incoming_message_service.rb
+++ b/app/services/twilio/incoming_message_service.rb
@@ -8,7 +8,7 @@ class Twilio::IncomingMessageService
set_contact
set_conversation
- @message = @conversation.messages.create(
+ @message = @conversation.messages.create!(
content: params[:Body],
account_id: @inbox.account_id,
inbox_id: @inbox.id,
diff --git a/app/services/twitter/direct_message_parser_service.rb b/app/services/twitter/direct_message_parser_service.rb
index e3679f278..5383e2e89 100644
--- a/app/services/twitter/direct_message_parser_service.rb
+++ b/app/services/twitter/direct_message_parser_service.rb
@@ -7,7 +7,7 @@ class Twitter::DirectMessageParserService < Twitter::WebhooksBaseService
set_inbox
ensure_contacts
set_conversation
- @message = @conversation.messages.create(
+ @message = @conversation.messages.create!(
content: message_create_data['message_data']['text'],
account_id: @inbox.account_id,
inbox_id: @inbox.id,
@@ -30,7 +30,7 @@ class Twitter::DirectMessageParserService < Twitter::WebhooksBaseService
def save_media_urls(file)
@message.content_attributes[:media_url] = file['media_url']
@message.content_attributes[:display_url] = file['display_url']
- @message.save
+ @message.save!
end
def direct_message_events_params
@@ -121,6 +121,6 @@ class Twitter::DirectMessageParserService < Twitter::WebhooksBaseService
content_type: media['type']
}
)
- @message.save
+ @message.save!
end
end
diff --git a/app/services/twitter/tweet_parser_service.rb b/app/services/twitter/tweet_parser_service.rb
index 82664ea5f..207f14abe 100644
--- a/app/services/twitter/tweet_parser_service.rb
+++ b/app/services/twitter/tweet_parser_service.rb
@@ -80,7 +80,7 @@ class Twitter::TweetParserService < Twitter::WebhooksBaseService
def create_message
find_or_create_contact(user)
set_conversation
- @conversation.messages.create(
+ @conversation.messages.create!(
account_id: @inbox.account_id,
sender: @contact,
content: tweet_text,
diff --git a/app/views/layouts/portal.html.erb b/app/views/layouts/portal.html.erb
new file mode 100644
index 000000000..8f0e8eff9
--- /dev/null
+++ b/app/views/layouts/portal.html.erb
@@ -0,0 +1,42 @@
+<%#
+# Application Layout
+
+This view template is used as the layout
+for every page that Administrate generates.
+
+By default, it renders:
+- Navigation
+- Content for a search bar
+ (if provided by a `content_for` block in a nested page)
+- Flashes
+- Links to stylesheets and JavaScripts
+%>
+
+
+
+
+
+
+
+
+ <%= javascript_pack_tag 'portal' %>
+ <%= stylesheet_pack_tag 'portal' %>
+
+
+ <%= csrf_meta_tags %>
+ <%= @portal.page_title%>
+
+
+
+
+
+
+ <%= render "public/api/v1/portals/header", portal: @portal %>
+ <%= yield %>
+ <%= render "public/api/v1/portals/footer" %>
+
+
+
+
+
+
diff --git a/app/views/public/api/v1/portals/_category-block.html.erb b/app/views/public/api/v1/portals/_category-block.html.erb
new file mode 100644
index 000000000..e7d8f1d32
--- /dev/null
+++ b/app/views/public/api/v1/portals/_category-block.html.erb
@@ -0,0 +1,44 @@
+
+
+
+
<%= category.articles.published.size %> articles
+
+
+ <% if category.articles.published.size == 0 %>
+
+ <% else %>
+ <% category.articles.published.take(5).each do |article| %>
+
+ <% end %>
+ <% end %>
+
+
+
+
diff --git a/app/views/public/api/v1/portals/_footer.html.erb b/app/views/public/api/v1/portals/_footer.html.erb
new file mode 100644
index 000000000..e7961d310
--- /dev/null
+++ b/app/views/public/api/v1/portals/_footer.html.erb
@@ -0,0 +1,6 @@
+
+
diff --git a/app/views/public/api/v1/portals/_header.html.erb b/app/views/public/api/v1/portals/_header.html.erb
new file mode 100644
index 000000000..d6a7b81a8
--- /dev/null
+++ b/app/views/public/api/v1/portals/_header.html.erb
@@ -0,0 +1,37 @@
+
+
+
+
diff --git a/app/views/public/api/v1/portals/_hero.html.erb b/app/views/public/api/v1/portals/_hero.html.erb
new file mode 100644
index 000000000..80f3469f9
--- /dev/null
+++ b/app/views/public/api/v1/portals/_hero.html.erb
@@ -0,0 +1,7 @@
+
+
+
+
<%= portal.header_text %>
+
Search for the articles here or browse the categories below.
+
+
diff --git a/app/views/public/api/v1/portals/articles/index.html.erb b/app/views/public/api/v1/portals/articles/index.html.erb
new file mode 100644
index 000000000..98f35e7c0
--- /dev/null
+++ b/app/views/public/api/v1/portals/articles/index.html.erb
@@ -0,0 +1,35 @@
+
+
+
+
+
+ <% @articles.each do |article| %>
+
+ <%= article.title %>
+
+
+
+
+
<%= article.author.name %>
+
+ <%= article.author.updated_at.strftime("%B %d %Y") %>
+
+
+
+ <% end %>
+
+
+
+
+
+
diff --git a/app/views/public/api/v1/portals/articles/show.html.erb b/app/views/public/api/v1/portals/articles/show.html.erb
new file mode 100644
index 000000000..bf3c6c302
--- /dev/null
+++ b/app/views/public/api/v1/portals/articles/show.html.erb
@@ -0,0 +1,35 @@
+
+
+
+
+
+ <%= @article.title %>
+
+
+
+
+
<%= @article.author.name %>
+
+ <%= @article.author.updated_at.strftime("%B %d %Y") %>
+
+
+
+
+
+
+
+
+
+
+
<%= @parsed_content %>
+
+
+
diff --git a/app/views/public/api/v1/portals/categories/_category-block.html.erb b/app/views/public/api/v1/portals/categories/_category-block.html.erb
new file mode 100644
index 000000000..e7d8f1d32
--- /dev/null
+++ b/app/views/public/api/v1/portals/categories/_category-block.html.erb
@@ -0,0 +1,44 @@
+
+
+
+
<%= category.articles.published.size %> articles
+
+
+ <% if category.articles.published.size == 0 %>
+
+ <% else %>
+ <% category.articles.published.take(5).each do |article| %>
+
+ <% end %>
+ <% end %>
+
+
+
+
diff --git a/app/views/public/api/v1/portals/categories/_hero.html.erb b/app/views/public/api/v1/portals/categories/_hero.html.erb
new file mode 100644
index 000000000..80f3469f9
--- /dev/null
+++ b/app/views/public/api/v1/portals/categories/_hero.html.erb
@@ -0,0 +1,7 @@
+
+
+
+
<%= portal.header_text %>
+
Search for the articles here or browse the categories below.
+
+
diff --git a/app/views/public/api/v1/portals/categories/index.html.erb b/app/views/public/api/v1/portals/categories/index.html.erb
new file mode 100644
index 000000000..5e6ffdd89
--- /dev/null
+++ b/app/views/public/api/v1/portals/categories/index.html.erb
@@ -0,0 +1,11 @@
+<%= render "hero", portal: @portal %>
+
+
+
+ <% @categories.each do |category| %>
+ <%= render "category-block", category: category, portal: @portal %>
+ <% end %>
+
+
+
+
diff --git a/app/views/public/api/v1/portals/categories/show.html.erb b/app/views/public/api/v1/portals/categories/show.html.erb
new file mode 100644
index 000000000..755e79c33
--- /dev/null
+++ b/app/views/public/api/v1/portals/categories/show.html.erb
@@ -0,0 +1,41 @@
+
+
+
+
+
+ <% if @category.articles.published.size == 0 %>
+
+ <% else %>
+ <% @category.articles.published.each do |article| %>
+
+ <% end %>
+ <% end %>
+
+
+
diff --git a/app/views/public/api/v1/portals/show.html.erb b/app/views/public/api/v1/portals/show.html.erb
new file mode 100644
index 000000000..93f214d4a
--- /dev/null
+++ b/app/views/public/api/v1/portals/show.html.erb
@@ -0,0 +1,12 @@
+
+<%= render "hero", portal: @portal %>
+
+
+
+ <% @portal.categories.each do |category| %>
+ <%= render "category-block", category: category, portal: @portal %>
+ <% end %>
+
+
+
+
diff --git a/config/routes.rb b/config/routes.rb
index b69faaac6..681227ef6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -287,11 +287,11 @@ Rails.application.routes.draw do
end
end
- get 'hc/:slug/:locale', to: 'public/api/v1/portals#show', format: 'json'
- get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index', format: 'json'
- get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show', format: 'json'
- get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index', format: 'json'
- get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show', format: 'json'
+ get 'hc/:slug/:locale', to: 'public/api/v1/portals#show'
+ get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index'
+ get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show'
+ get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index'
+ get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show'
# ----------------------------------------------------------------------
# Used in mailer templates
diff --git a/lib/chatwoot_hub.rb b/lib/chatwoot_hub.rb
index f909527fc..68bae8267 100644
--- a/lib/chatwoot_hub.rb
+++ b/lib/chatwoot_hub.rb
@@ -7,7 +7,7 @@ class ChatwootHub
def self.installation_identifier
identifier = InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER')&.value
- identifier ||= InstallationConfig.create(name: 'INSTALLATION_IDENTIFIER', value: SecureRandom.uuid).value
+ identifier ||= InstallationConfig.create!(name: 'INSTALLATION_IDENTIFIER', value: SecureRandom.uuid).value
identifier
end
diff --git a/lib/integrations/csml/processor_service.rb b/lib/integrations/csml/processor_service.rb
index e9f7e85ef..3a8f21077 100644
--- a/lib/integrations/csml/processor_service.rb
+++ b/lib/integrations/csml/processor_service.rb
@@ -84,7 +84,7 @@ class Integrations::Csml::ProcessorService < Integrations::BotProcessorService
end
def process_text_messages(message_payload, conversation)
- conversation.messages.create(
+ conversation.messages.create!(
{
message_type: :outgoing,
account_id: conversation.account_id,
@@ -99,7 +99,7 @@ class Integrations::Csml::ProcessorService < Integrations::BotProcessorService
buttons = message_payload['content']['buttons'].map do |button|
{ title: button['content']['title'], value: button['content']['payload'] }
end
- conversation.messages.create(
+ conversation.messages.create!(
{
message_type: :outgoing,
account_id: conversation.account_id,
diff --git a/lib/integrations/dialogflow/processor_service.rb b/lib/integrations/dialogflow/processor_service.rb
index b23b8e082..384c41c11 100644
--- a/lib/integrations/dialogflow/processor_service.rb
+++ b/lib/integrations/dialogflow/processor_service.rb
@@ -43,10 +43,10 @@ class Integrations::Dialogflow::ProcessorService < Integrations::BotProcessorSer
return if content_params.blank?
conversation = message.conversation
- conversation.messages.create(content_params.merge({
- message_type: :outgoing,
- account_id: conversation.account_id,
- inbox_id: conversation.inbox_id
- }))
+ conversation.messages.create!(content_params.merge({
+ message_type: :outgoing,
+ account_id: conversation.account_id,
+ inbox_id: conversation.inbox_id
+ }))
end
end
diff --git a/lib/integrations/slack/incoming_message_builder.rb b/lib/integrations/slack/incoming_message_builder.rb
index 966d897f9..a2f75ce3a 100644
--- a/lib/integrations/slack/incoming_message_builder.rb
+++ b/lib/integrations/slack/incoming_message_builder.rb
@@ -83,7 +83,7 @@ class Integrations::Slack::IncomingMessageBuilder
def create_message
return unless conversation
- @message = conversation.messages.create(
+ @message = conversation.messages.create!(
message_type: :outgoing,
account_id: conversation.account_id,
inbox_id: conversation.inbox_id,
diff --git a/package.json b/package.json
index d4a4935d3..9ee0819f2 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"@chatwoot/utils": "^0.0.10",
"@hcaptcha/vue-hcaptcha": "^0.3.2",
"@rails/actioncable": "6.1.3",
+ "@rails/ujs": "^7.0.3-1",
"@rails/webpacker": "5.3.0",
"@sentry/tracing": "^6.19.7",
"@sentry/vue": "^6.19.7",
@@ -50,6 +51,7 @@
"semver": "7.3.5",
"spinkit": "~1.2.5",
"tailwindcss": "^1.9.6",
+ "turbolinks": "^5.2.0",
"url-loader": "^2.0.0",
"v-tooltip": "~2.1.3",
"videojs-record": "^4.5.0",
diff --git a/spec/controllers/api/v1/widget/labels_controller_spec.rb b/spec/controllers/api/v1/widget/labels_controller_spec.rb
index bdd47f9c9..fe242c29b 100644
--- a/spec/controllers/api/v1/widget/labels_controller_spec.rb
+++ b/spec/controllers/api/v1/widget/labels_controller_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe '/api/v1/widget/labels', type: :request do
context 'with correct website token and a defined label' do
before do
- account.labels.create(title: 'customer-support')
+ account.labels.create!(title: 'customer-support')
end
it 'add the label to the conversation' do
diff --git a/spec/controllers/public/api/v1/portals/articles_controller_spec.rb b/spec/controllers/public/api/v1/portals/articles_controller_spec.rb
index fcee25486..53c455c1b 100644
--- a/spec/controllers/public/api/v1/portals/articles_controller_spec.rb
+++ b/spec/controllers/public/api/v1/portals/articles_controller_spec.rb
@@ -20,10 +20,6 @@ RSpec.describe 'Public Articles API', type: :request do
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/articles"
expect(response).to have_http_status(:success)
- json_response = JSON.parse(response.body)
-
- expect(json_response['payload'].length).to eql portal.articles.count
- expect(json_response['meta']['articles_count']).to be json_response['payload'].size
end
it 'get all articles with searched text query' do
@@ -39,9 +35,6 @@ RSpec.describe 'Public Articles API', type: :request do
headers: agent.create_new_auth_token,
params: { query: 'funny' }
expect(response).to have_http_status(:success)
- json_response = JSON.parse(response.body)
- expect(json_response['payload'].count).to be 1
- expect(json_response['meta']['articles_count']).to be json_response['payload'].size
end
end
@@ -50,9 +43,6 @@ RSpec.describe 'Public Articles API', type: :request do
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/#{article.id}"
expect(response).to have_http_status(:success)
- json_response = JSON.parse(response.body)
-
- expect(json_response['title']).to eql article.title
end
end
end
diff --git a/spec/controllers/public/api/v1/portals_controller_spec.rb b/spec/controllers/public/api/v1/portals_controller_spec.rb
index c52f6ba94..7234c62b5 100644
--- a/spec/controllers/public/api/v1/portals_controller_spec.rb
+++ b/spec/controllers/public/api/v1/portals_controller_spec.rb
@@ -14,9 +14,6 @@ RSpec.describe 'Public Portals API', type: :request do
get "/hc/#{portal.slug}/en"
expect(response).to have_http_status(:success)
- json_response = JSON.parse(response.body)
- expect(json_response['slug']).to eql 'test-portal'
- expect(json_response['meta']['articles_count']).to be 0
end
it 'Throws unauthorised error for unknown domain' do
diff --git a/spec/listeners/automation_rule_listener_spec.rb b/spec/listeners/automation_rule_listener_spec.rb
index 5ca377df8..2b31fa8ae 100644
--- a/spec/listeners/automation_rule_listener_spec.rb
+++ b/spec/listeners/automation_rule_listener_spec.rb
@@ -43,7 +43,7 @@ describe AutomationRuleListener do
])
file = fixture_file_upload(Rails.root.join('spec/assets/avatar.png'), 'image/png')
automation_rule.files.attach(file)
- automation_rule.save
+ automation_rule.save!
end
describe '#conversation_updated with contacts attributes' do
diff --git a/spec/mailboxes/reply_mailbox_spec.rb b/spec/mailboxes/reply_mailbox_spec.rb
index c73de877c..a383e3e5c 100644
--- a/spec/mailboxes/reply_mailbox_spec.rb
+++ b/spec/mailboxes/reply_mailbox_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe ReplyMailbox, type: :mailbox do
before do
# this UUID is hardcoded in the reply.eml, that's why we are updating this
conversation.uuid = '6bdc3f4d-0bec-4515-a284-5d916fdde489'
- conversation.save
+ conversation.save!
described_subject
end
@@ -129,7 +129,7 @@ RSpec.describe ReplyMailbox, type: :mailbox do
before do
# this UUID is hardcoded in the reply.eml, that's why we are updating this
conversation.uuid = '6bdc3f4d-0bec-4515-a284-5d916fdde489'
- conversation.save
+ conversation.save!
end
it 'add the mail content as new message on the conversation' do
diff --git a/spec/mailboxes/support_mailbox_spec.rb b/spec/mailboxes/support_mailbox_spec.rb
index 88ded0831..59217b212 100644
--- a/spec/mailboxes/support_mailbox_spec.rb
+++ b/spec/mailboxes/support_mailbox_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe SupportMailbox, type: :mailbox do
before do
# this email is hardcoded in the support.eml, that's why we are updating this
channel_email.email = 'care@example.com'
- channel_email.save
+ channel_email.save!
end
describe 'covers email address format' do
@@ -121,7 +121,7 @@ RSpec.describe SupportMailbox, type: :mailbox do
before do
# this email is hardcoded eml fixture file that's why we are updating this
channel_email.email = 'support@chatwoot.com'
- channel_email.save
+ channel_email.save!
end
it 'create new contact with original sender' do
diff --git a/spec/mailers/conversation_reply_mailer_spec.rb b/spec/mailers/conversation_reply_mailer_spec.rb
index cb3c4a9f9..67645e101 100644
--- a/spec/mailers/conversation_reply_mailer_spec.rb
+++ b/spec/mailers/conversation_reply_mailer_spec.rb
@@ -54,15 +54,15 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
it 'renders the subject in conversation as reply' do
conversation.additional_attributes = { 'mail_subject': 'Mail Subject' }
- conversation.save
- new_message.save
+ conversation.save!
+ new_message.save!
expect(mail.subject).to eq('Re: Mail Subject')
end
it 'not have private notes' do
# make the message private
private_message.private = true
- private_message.save
+ private_message.save!
expect(mail.body.decoded).not_to include(private_message.content)
expect(mail.body.decoded).to include(message.content)
@@ -104,7 +104,7 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
let(:mail) { described_class.reply_without_summary(message_2.conversation, message_2.id).deliver_now }
before do
- message_2.save
+ message_2.save!
end
it 'renders the default subject' do
@@ -113,14 +113,14 @@ RSpec.describe ConversationReplyMailer, type: :mailer do
it 'renders the subject in conversation' do
conversation.additional_attributes = { 'mail_subject': 'Mail Subject' }
- conversation.save
+ conversation.save!
expect(mail.subject).to eq('Mail Subject')
end
it 'not have private notes' do
# make the message private
private_message.private = true
- private_message.save
+ private_message.save!
expect(mail.body.decoded).not_to include(private_message.content)
end
diff --git a/spec/models/campaign_spec.rb b/spec/models/campaign_spec.rb
index 53890458e..f99f18390 100644
--- a/spec/models/campaign_spec.rb
+++ b/spec/models/campaign_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Campaign, type: :model do
let(:campaign) { build(:campaign, inbox: website_inbox, display_id: nil, trigger_rules: { url: 'https://test.com' }) }
before do
- campaign.save
+ campaign.save!
campaign.reload
end
diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb
index 22979b7c6..3271bdf19 100644
--- a/spec/models/conversation_spec.rb
+++ b/spec/models/conversation_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Conversation, type: :model do
let(:conversation) { build(:conversation, display_id: nil) }
before do
- conversation.save
+ conversation.save!
conversation.reload
end
diff --git a/spec/workers/conversation_reply_email_worker_spec.rb b/spec/workers/conversation_reply_email_worker_spec.rb
index 936242a06..3483c04a5 100644
--- a/spec/workers/conversation_reply_email_worker_spec.rb
+++ b/spec/workers/conversation_reply_email_worker_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe ConversationReplyEmailWorker, type: :worker do
end
it 'calls ConversationSummaryMailer#reply_without_summary when last incoming message was from email' do
- message.save
+ message.save!
described_class.new.perform(1, message.id)
expect(mailer).to have_received(:reply_without_summary)
end
diff --git a/yarn.lock b/yarn.lock
index 9a896cb10..6d2234d26 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1878,6 +1878,11 @@
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.3.tgz#c8a67ec4d22ecd6931f7ebd98143fddbc815419a"
integrity sha512-m02524MR9cTnUNfGz39Lkx9jVvuL0tle4O7YgvouJ7H83FILxzG1nQ5jw8pAjLAr9XQGu+P1sY4SKE3zyhCNjw==
+"@rails/ujs@^7.0.3-1":
+ version "7.0.3-1"
+ resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.0.3-1.tgz#0a0f4f2b22b887bcbf6e0b0a72b8c86665cd31d9"
+ integrity sha512-g3LgpBAsWmW97xFxh5OTDgyEJLt63fEENJUYb/iNFRXY6aKLI/by6MjFw7x492DSP/+vKQa3oMEdNnjI9+yZgQ==
+
"@rails/webpacker@5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.3.0.tgz#9d7a615735f850572b9c5e2ad4c57f4af70d70fd"
@@ -15087,6 +15092,11 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
+turbolinks@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.2.0.tgz#e6877a55ea5c1cb3bb225f0a4ae303d6d32ff77c"
+ integrity sha512-pMiez3tyBo6uRHFNNZoYMmrES/IaGgMhQQM+VFF36keryjb5ms0XkVpmKHkfW/4Vy96qiGW3K9bz0tF5sK9bBw==
+
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"