diff --git a/app/builders/notification_subscription_builder.rb b/app/builders/notification_subscription_builder.rb index af75ef2fd..0dede970f 100644 --- a/app/builders/notification_subscription_builder.rb +++ b/app/builders/notification_subscription_builder.rb @@ -25,7 +25,7 @@ class NotificationSubscriptionBuilder end def build_identifier_subscription - @identifier_subscription = user.notification_subscriptions.create(params.merge(identifier: identifier)) + @identifier_subscription = user.notification_subscriptions.create!(params.merge(identifier: identifier)) end def update_identifier_subscription diff --git a/app/controllers/api/v1/accounts/automation_rules_controller.rb b/app/controllers/api/v1/accounts/automation_rules_controller.rb index 859c8a4bf..0bb78d3ea 100644 --- a/app/controllers/api/v1/accounts/automation_rules_controller.rb +++ b/app/controllers/api/v1/accounts/automation_rules_controller.rb @@ -49,7 +49,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont def clone automation_rule = Current.account.automation_rules.find_by(id: params[:automation_rule_id]) new_rule = automation_rule.dup - new_rule.save + new_rule.save! @automation_rule = new_rule end diff --git a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb index 55a9456cf..e6e5b63c3 100644 --- a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb +++ b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb @@ -44,7 +44,7 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts: phone_number: phone_number, medium: medium ) - @inbox = Current.account.inboxes.create( + @inbox = Current.account.inboxes.create!( name: permitted_params[:name], channel: @twilio_channel ) diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 7ef3c0d87..1c56e9c04 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -135,7 +135,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController inbox = Current.account.inboxes.find(params[:inbox_id]) source_id = params[:source_id] || SecureRandom.uuid - ContactInbox.create(contact: @contact, inbox: inbox, source_id: source_id) + ContactInbox.create!(contact: @contact, inbox: inbox, source_id: source_id) end def permitted_params diff --git a/app/controllers/api/v1/widget/conversations_controller.rb b/app/controllers/api/v1/widget/conversations_controller.rb index a01d33cdf..83206de90 100644 --- a/app/controllers/api/v1/widget/conversations_controller.rb +++ b/app/controllers/api/v1/widget/conversations_controller.rb @@ -9,7 +9,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController ActiveRecord::Base.transaction do process_update_contact @conversation = create_conversation - conversation.messages.create(message_params) + conversation.messages.create!(message_params) end end @@ -59,7 +59,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController unless conversation.resolved? conversation.status = :resolved - conversation.save + conversation.save! end head :ok end diff --git a/app/controllers/devise_overrides/sessions_controller.rb b/app/controllers/devise_overrides/sessions_controller.rb index 289684f17..3bbc6ee3b 100644 --- a/app/controllers/devise_overrides/sessions_controller.rb +++ b/app/controllers/devise_overrides/sessions_controller.rb @@ -23,7 +23,7 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle def authenticate_resource_with_sso_token @token = @resource.create_token - @resource.save + @resource.save! sign_in(:user, @resource, store: false, bypass: false) # invalidate the token after the user is signed in diff --git a/app/controllers/public/api/v1/portals/articles_controller.rb b/app/controllers/public/api/v1/portals/articles_controller.rb index a5d98159c..f2df4add4 100644 --- a/app/controllers/public/api/v1/portals/articles_controller.rb +++ b/app/controllers/public/api/v1/portals/articles_controller.rb @@ -3,6 +3,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController before_action :set_portal before_action :set_category before_action :set_article, only: [:show] + layout 'portal' def index @articles = @portal.articles @@ -15,6 +16,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController def set_article @article = @category.articles.find(params[:id]) + @parsed_content = render_article_content(@article.content) end def set_category @@ -28,4 +30,10 @@ class Public::Api::V1::Portals::ArticlesController < PublicController def list_params params.permit(:query) end + + def render_article_content(content) + # rubocop:disable Rails/OutputSafety + CommonMarker.render_html(content).html_safe + # rubocop:enable Rails/OutputSafety + end end diff --git a/app/controllers/public/api/v1/portals/categories_controller.rb b/app/controllers/public/api/v1/portals/categories_controller.rb index 1f8e2093e..a36be13a1 100644 --- a/app/controllers/public/api/v1/portals/categories_controller.rb +++ b/app/controllers/public/api/v1/portals/categories_controller.rb @@ -2,6 +2,7 @@ class Public::Api::V1::Portals::CategoriesController < PublicController before_action :ensure_custom_domain_request, only: [:show, :index] before_action :set_portal before_action :set_category, only: [:show] + layout 'portal' def index @categories = @portal.categories @@ -12,7 +13,7 @@ class Public::Api::V1::Portals::CategoriesController < PublicController private def set_category - @category = @portal.categories.find_by!(locale: params[:locale]) + @category = @portal.categories.find_by!(locale: params[:locale], slug: params[:category_slug]) end def set_portal diff --git a/app/controllers/public/api/v1/portals_controller.rb b/app/controllers/public/api/v1/portals_controller.rb index bd3b4a911..276549eae 100644 --- a/app/controllers/public/api/v1/portals_controller.rb +++ b/app/controllers/public/api/v1/portals_controller.rb @@ -1,6 +1,7 @@ class Public::Api::V1::PortalsController < PublicController before_action :ensure_custom_domain_request, only: [:show] before_action :set_portal + layout 'portal' def show; end diff --git a/app/controllers/twitter/callbacks_controller.rb b/app/controllers/twitter/callbacks_controller.rb index 32fffbceb..0c2dc8263 100644 --- a/app/controllers/twitter/callbacks_controller.rb +++ b/app/controllers/twitter/callbacks_controller.rb @@ -44,12 +44,12 @@ class Twitter::CallbacksController < Twitter::BaseController end def create_inbox - twitter_profile = account.twitter_profiles.create( + twitter_profile = account.twitter_profiles.create!( twitter_access_token: parsed_body['oauth_token'], twitter_access_token_secret: parsed_body['oauth_token_secret'], profile_id: parsed_body['user_id'] ) - account.inboxes.create( + account.inboxes.create!( name: parsed_body['screen_name'], channel: twitter_profile ) diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index 9048779ef..c7c145399 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -285,8 +285,6 @@ export default { } .secondary-menu .nested.vertical.menu { - overflow-y: auto; - height: 100%; margin-left: var(--space-small); } diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue index c303268c9..e68fcc3a4 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue @@ -245,6 +245,8 @@ export default { @import '~dashboard/assets/scss/woot'; .secondary-menu { + display: flex; + flex-direction: column; background: var(--white); border-right: 1px solid var(--s-50); height: 100%; @@ -267,7 +269,6 @@ export default { .menu { padding: var(--space-small); overflow-y: auto; - height: 94%; } } diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index 5c288ed68..74cd9de4d 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -37,17 +37,6 @@ size="small" /> - import FileUpload from 'vue-upload-component'; import * as ActiveStorage from 'activestorage'; -import { - hasPressedAltAndWKey, - hasPressedAltAndAKey, -} from 'shared/helpers/KeyboardHelpers'; +import { hasPressedAltAndAKey } from 'shared/helpers/KeyboardHelpers'; import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import uiSettingsMixin from 'dashboard/mixins/uiSettings'; import inboxMixin from 'shared/mixins/inboxMixin'; @@ -207,10 +193,6 @@ export default { type: Boolean, default: false, }, - setFormatMode: { - type: Function, - default: () => {}, - }, isFormatMode: { type: Boolean, default: false, @@ -219,10 +201,6 @@ export default { type: Boolean, default: false, }, - enableRichEditor: { - type: Boolean, - default: false, - }, enterToSendEnabled: { type: Boolean, default: true, @@ -296,16 +274,10 @@ export default { }, methods: { handleKeyEvents(e) { - if (hasPressedAltAndWKey(e)) { - this.toggleFormatMode(); - } if (hasPressedAltAndAKey(e)) { this.$refs.upload.$children[1].$el.click(); } }, - toggleFormatMode() { - this.setFormatMode(!this.isFormatMode); - }, toggleEnterToSend() { this.$emit('toggleEnterToSend', !this.enterToSendEnabled); }, diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 286ac0a88..d24b31d30 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -107,10 +107,8 @@ :recording-audio-duration-text="recordingAudioDurationText" :recording-audio-state="recordingAudioState" :is-recording-audio="isRecordingAudio" - :set-format-mode="setFormatMode" :is-on-private-note="isOnPrivateNote" :is-format-mode="showRichContentEditor" - :enable-rich-editor="isRichEditorEnabled" :enter-to-send-enabled="enterToSendEnabled" :enable-multiple-file-upload="enableMultipleFileUpload" :has-whatsapp-templates="hasWhatsappTemplates" @@ -229,17 +227,10 @@ export default { accountId: 'getCurrentAccountId', }), showRichContentEditor() { - if (this.isOnPrivateNote) { + if (this.isOnPrivateNote || this.isRichEditorEnabled) { return true; } - if (this.isRichEditorEnabled) { - const { - display_rich_content_editor: displayRichContentEditor, - } = this.uiSettings; - - return displayRichContentEditor; - } return false; }, assignedAgent: { @@ -375,7 +366,7 @@ export default { ); }, isRichEditorEnabled() { - return this.isAWebWidgetInbox || this.isAnEmailChannel; + return this.isAWebWidgetInbox || this.isAnEmailChannel || this.isAPIInbox; }, showAudioRecorder() { return !this.isOnPrivateNote && this.showFileUpload; @@ -799,9 +790,6 @@ export default { return messagePayload; }, - setFormatMode(value) { - this.updateUISettings({ display_rich_content_editor: value }); - }, setCcEmails(value) { this.bccEmails = value.bccEmails; this.ccEmails = value.ccEmails; diff --git a/app/javascript/dashboard/components/widgets/modal/constants.js b/app/javascript/dashboard/components/widgets/modal/constants.js index faec60dc1..5741d954e 100644 --- a/app/javascript/dashboard/components/widgets/modal/constants.js +++ b/app/javascript/dashboard/components/widgets/modal/constants.js @@ -65,12 +65,6 @@ export const SHORTCUT_KEYS = [ firstkey: 'Alt / ⌥', secondKey: 'P', }, - { - id: 12, - label: 'TOGGLE_RICH_CONTENT_EDITOR', - firstkey: 'Alt / ⌥', - secondKey: 'W', - }, { id: 13, label: 'SWITCH_TO_REPLY', diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 038af64a5..88c967a55 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -253,7 +253,6 @@ "GO_TO_SETTINGS": "Go to Settings", "SWITCH_CONVERSATION_STATUS": "Switch to the next conversation status", "SWITCH_TO_PRIVATE_NOTE": "Switch to Private Note", - "TOGGLE_RICH_CONTENT_EDITOR": "Toggle Rich Content editor", "SWITCH_TO_REPLY": "Switch to Reply", "TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown" }, diff --git a/app/javascript/dashboard/mixins/specs/uiSettings.spec.js b/app/javascript/dashboard/mixins/specs/uiSettings.spec.js index 6b1118ab8..aad1fe3d8 100644 --- a/app/javascript/dashboard/mixins/specs/uiSettings.spec.js +++ b/app/javascript/dashboard/mixins/specs/uiSettings.spec.js @@ -16,7 +16,6 @@ describe('uiSettingsMixin', () => { actions = { updateUISettings: jest.fn(), toggleSidebarUIState: jest.fn() }; getters = { getUISettings: () => ({ - display_rich_content_editor: false, enter_to_send_enabled: false, is_ct_labels_open: true, conversation_sidebar_items_order: DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER, @@ -34,7 +33,6 @@ describe('uiSettingsMixin', () => { }; const wrapper = shallowMount(Component, { store, localVue }); expect(wrapper.vm.uiSettings).toEqual({ - display_rich_content_editor: false, enter_to_send_enabled: false, is_ct_labels_open: true, conversation_sidebar_items_order: DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER, @@ -55,7 +53,6 @@ describe('uiSettingsMixin', () => { expect.anything(), { uiSettings: { - display_rich_content_editor: false, enter_to_send_enabled: true, is_ct_labels_open: true, conversation_sidebar_items_order: DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER, @@ -80,7 +77,6 @@ describe('uiSettingsMixin', () => { expect.anything(), { uiSettings: { - display_rich_content_editor: false, enter_to_send_enabled: false, is_ct_labels_open: false, conversation_sidebar_items_order: DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER, diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue index 9a9e670b1..2228320bb 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue @@ -2,6 +2,7 @@
+

{{ headerTitle }}

@@ -173,11 +174,13 @@ export default { } .search-wrap { - width: 400px; + max-width: 400px; + min-width: 150px; display: flex; align-items: center; position: relative; margin-right: var(--space-small); + margin-left: var(--space-small); .search-icon { position: absolute; diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue index f410fd5a0..ce4808099 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader.vue @@ -4,6 +4,7 @@ diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue index 347318fcf..162e9343e 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue @@ -6,17 +6,16 @@ @open-key-shortcut-modal="toggleKeyShortcutModal" @close-key-shortcut-modal="closeKeyShortcutModal" /> -
- -
+
@@ -33,13 +32,15 @@ v-if="showPortalPopover" :portals="portals" :active-portal-slug="selectedPortalSlug" + :active-locale="selectedLocaleInPortal" @close-popover="closePortalPopover" />
@@ -96,6 +97,9 @@ export default { return this.$store.getters['portals/allPortals'][0]; }, + selectedLocaleInPortal() { + return this.$route.params.locale || this.defaultPortalLocale; + }, sidebarClassName() { if (this.isOnDesktop) { return ''; @@ -120,7 +124,7 @@ export default { selectedPortalSlug() { return this.selectedPortal ? this.selectedPortal?.slug : ''; }, - selectedPortalLocale() { + defaultPortalLocale() { return this.selectedPortal ? this.selectedPortal?.meta?.default_locale : ''; @@ -142,7 +146,7 @@ export default { key: 'list_all_locale_articles', count: allArticlesCount, toState: frontendURL( - `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedPortalLocale}/articles` + `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles` ), toolTip: 'All Articles', toStateName: 'list_all_locale_articles', @@ -153,7 +157,7 @@ export default { key: 'list_mine_articles', count: mineArticlesCount, toState: frontendURL( - `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedPortalLocale}/articles/mine` + `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles/mine` ), toolTip: 'My articles', toStateName: 'list_mine_articles', @@ -164,7 +168,7 @@ export default { key: 'list_draft_articles', count: draftArticlesCount, toState: frontendURL( - `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedPortalLocale}/articles/draft` + `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles/draft` ), toolTip: 'Draft', toStateName: 'list_draft_articles', @@ -175,7 +179,7 @@ export default { key: 'list_archived_articles', count: archivedArticlesCount, toState: frontendURL( - `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedPortalLocale}/articles/archived` + `accounts/${this.accountId}/portals/${this.selectedPortalSlug}/${this.selectedLocaleInPortal}/articles/archived` ), toolTip: 'Archived', toStateName: 'list_archived_articles', diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue index 94d9a0778..1f5eebe0e 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalPopover.vue @@ -34,18 +34,11 @@ :key="portal.id" :portal="portal" :active-portal-slug="activePortalSlug" + :active-locale="activeLocale" :active="portal.slug === activePortalSlug" @open-portal-page="onPortalSelect" />
-
- - {{ $t('HELP_CENTER.PORTAL.POPOVER.CANCEL_BUTTON_LABEL') }} - - - {{ $t('HELP_CENTER.PORTAL.POPOVER.CHOOSE_LOCALE_BUTTON') }} - -
@@ -66,6 +59,10 @@ export default { type: String, default: '', }, + activeLocale: { + type: String, + default: '', + }, }, methods: { @@ -125,12 +122,5 @@ export default { } } } - - footer { - display: flex; - justify-content: end; - align-items: center; - gap: var(--space-small); - } } diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue index b93a82025..91b8a0467 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue @@ -19,13 +19,13 @@ />
-
+
{{ $t('HELP_CENTER.PORTAL.CHOOSE_LOCALE_LABEL') }}
  • -
    +
    {{ 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.name %> +

    <%= category.articles.published.size %> articles +
    +
    + <% if category.articles.published.size == 0 %> +
    +

    No articles here

    +
    + <% else %> + <% category.articles.published.take(5).each do |article| %> +
    + <%= article.title %> + + + + + +
    + <% 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.name %> +

    <%= category.articles.published.size %> articles +
    +
    + <% if category.articles.published.size == 0 %> +
    +

    No articles here

    +
    + <% else %> + <% category.articles.published.take(5).each do |article| %> +
    + <%= article.title %> + + + + + +
    + <% 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 @@ + +
    +
    + <%= @portal.name %> Home + / + +
    +

    + <%= @category.name %>

    + <%= @category.articles.published.size %> articles +
    +
    + +
    +
    + +
    + <% if @category.articles.published.size == 0 %> +
    +

    No articles here

    +
    + <% else %> + <% @category.articles.published.each do |article| %> +
    + <%= article.title %> + + + + + +
    + <% 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"