diff --git a/.codeclimate.yml b/.codeclimate.yml index 974681a09..d8b8d985b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -54,3 +54,5 @@ exclude_patterns: - 'app/javascript/widget/i18n/index.js' - 'app/javascript/survey/i18n/index.js' - 'app/javascript/shared/constants/locales.js' + - 'app/javascript/dashboard/helper/specs/macrosFixtures.js' + - 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js' diff --git a/.env.example b/.env.example index 12262576e..830ee7bce 100644 --- a/.env.example +++ b/.env.example @@ -34,6 +34,11 @@ REDIS_SENTINELS= # You can find list of master using "SENTINEL masters" command REDIS_SENTINEL_MASTER_NAME= +# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels +# Use the following environment variable to customize passwords for sentinels. +# Use empty string if sentinels are configured with out passwords +# REDIS_SENTINEL_PASSWORD= + # Redis premium breakage in heroku fix # enable the following configuration # ref: https://github.com/chatwoot/chatwoot/issues/2420 diff --git a/app/builders/messages/message_builder.rb b/app/builders/messages/message_builder.rb index e9bf0802b..69ed786ce 100644 --- a/app/builders/messages/message_builder.rb +++ b/app/builders/messages/message_builder.rb @@ -35,7 +35,13 @@ class Messages::MessageBuilder file: uploaded_attachment ) - attachment.file_type = file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile) + attachment.file_type = if uploaded_attachment.is_a?(String) + file_type_by_signed_id( + uploaded_attachment + ) + else + file_type(uploaded_attachment&.content_type) + end end end diff --git a/app/controllers/api/v1/accounts/articles_controller.rb b/app/controllers/api/v1/accounts/articles_controller.rb index d650b19d9..a7891ffd4 100644 --- a/app/controllers/api/v1/accounts/articles_controller.rb +++ b/app/controllers/api/v1/accounts/articles_controller.rb @@ -5,9 +5,10 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController before_action :set_current_page, only: [:index] def index - @articles_count = @portal.articles.count - @articles = @portal.articles - @articles = @articles.search(list_params) if list_params.present? + @portal_articles = @portal.articles + @all_articles = @portal_articles.search(list_params) + @articles_count = @all_articles.count + @articles = @all_articles.page(@current_page) end def create @@ -37,7 +38,7 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController end def portal - @portal ||= Current.account.portals.find_by(slug: params[:portal_id]) + @portal ||= Current.account.portals.find_by!(slug: params[:portal_id]) end def article_params diff --git a/app/controllers/api/v1/accounts/categories_controller.rb b/app/controllers/api/v1/accounts/categories_controller.rb index bc18b61b8..e28e601d5 100644 --- a/app/controllers/api/v1/accounts/categories_controller.rb +++ b/app/controllers/api/v1/accounts/categories_controller.rb @@ -5,6 +5,7 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle before_action :set_current_page, only: [:index] def index + @current_locale = params[:locale] @categories = @portal.categories.search(params) end diff --git a/app/controllers/api/v1/accounts/macros_controller.rb b/app/controllers/api/v1/accounts/macros_controller.rb index e7946a54e..3e812ed63 100644 --- a/app/controllers/api/v1/accounts/macros_controller.rb +++ b/app/controllers/api/v1/accounts/macros_controller.rb @@ -14,6 +14,8 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController render json: { error: @macro.errors.messages }, status: :unprocessable_entity and return unless @macro.valid? @macro.save! + process_attachments + @macro end def show @@ -25,10 +27,21 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController head :ok end + def attach_file + file_blob = ActiveStorage::Blob.create_and_upload!( + key: nil, + io: params[:attachment].tempfile, + filename: params[:attachment].original_filename, + content_type: params[:attachment].content_type + ) + render json: { blob_key: file_blob.key, blob_id: file_blob.id } + end + def update ActiveRecord::Base.transaction do @macro.update!(macros_with_user) @macro.set_visibility(current_user, permitted_params) + process_attachments @macro.save! rescue StandardError => e Rails.logger.error e @@ -42,6 +55,17 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController head :ok end + def process_attachments + actions = @macro.actions.filter_map { |k, _v| k if k['action_name'] == 'send_attachment' } + return if actions.blank? + + actions.each do |action| + blob_id = action['action_params'] + blob = ActiveStorage::Blob.find_by(id: blob_id) + @macro.files.attach(blob) + end + end + def permitted_params params.permit( :name, :account_id, :visibility, diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb index 6ac000b58..62a98a872 100644 --- a/app/controllers/api/v1/accounts/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -14,7 +14,10 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController @portal.members << agents end - def show; end + def show + @all_articles = @portal.articles + @articles = @all_articles.search(locale: params[:locale]) + end def create @portal = Current.account.portals.build(portal_params) diff --git a/app/controllers/platform/api/v1/accounts_controller.rb b/app/controllers/platform/api/v1/accounts_controller.rb index 9c8f60f38..78143410f 100644 --- a/app/controllers/platform/api/v1/accounts_controller.rb +++ b/app/controllers/platform/api/v1/accounts_controller.rb @@ -3,16 +3,12 @@ class Platform::Api::V1::AccountsController < PlatformController @resource = Account.new(account_params) @resource.save! @platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource) - render json: @resource end - def show - render json: @resource - end + def show; end def update @resource.update!(account_params) - render json: @resource end def destroy @@ -27,6 +23,14 @@ class Platform::Api::V1::AccountsController < PlatformController end def account_params - params.permit(:name, :locale) + if permitted_params[:enabled_features] + return permitted_params.except(:enabled_features).merge(selected_feature_flags: permitted_params[:enabled_features].map(&:to_sym)) + end + + permitted_params + end + + def permitted_params + params.permit(:name, :locale, enabled_features: [], limits: {}) end end diff --git a/app/controllers/public/api/v1/portals/articles_controller.rb b/app/controllers/public/api/v1/portals/articles_controller.rb index 0dfa2ea9b..ae0fb2b6a 100644 --- a/app/controllers/public/api/v1/portals/articles_controller.rb +++ b/app/controllers/public/api/v1/portals/articles_controller.rb @@ -1,7 +1,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController before_action :ensure_custom_domain_request, only: [:show, :index] before_action :portal - before_action :set_category + before_action :set_category, except: [:index] before_action :set_article, only: [:show] layout 'portal' @@ -20,7 +20,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController end def set_category - @category = @portal.categories.find_by!(slug: params[:category_slug]) + @category = @portal.categories.find_by!(slug: params[:category_slug]) if params[:category_slug].present? end def portal diff --git a/app/helpers/file_type_helper.rb b/app/helpers/file_type_helper.rb index db3f6249d..03b807aad 100644 --- a/app/helpers/file_type_helper.rb +++ b/app/helpers/file_type_helper.rb @@ -8,6 +8,12 @@ module FileTypeHelper :file end + # Used in case of DIRECT_UPLOADS_ENABLED=true + def file_type_by_signed_id(signed_id) + blob = ActiveStorage::Blob.find_signed(signed_id) + file_type(blob&.content_type) + end + def image_file?(content_type) [ 'image/jpeg', diff --git a/app/javascript/dashboard/api/helpCenter/categories.js b/app/javascript/dashboard/api/helpCenter/categories.js index b6eae3f32..01658497e 100644 --- a/app/javascript/dashboard/api/helpCenter/categories.js +++ b/app/javascript/dashboard/api/helpCenter/categories.js @@ -7,8 +7,8 @@ class CategoriesAPI extends PortalsAPI { super('categories', { accountScoped: true }); } - get({ portalSlug }) { - return axios.get(`${this.url}/${portalSlug}/categories`); + get({ portalSlug, locale }) { + return axios.get(`${this.url}/${portalSlug}/categories?locale=${locale}`); } create({ portalSlug, categoryObj }) { diff --git a/app/javascript/dashboard/api/helpCenter/portals.js b/app/javascript/dashboard/api/helpCenter/portals.js index 520453540..28220b0b5 100644 --- a/app/javascript/dashboard/api/helpCenter/portals.js +++ b/app/javascript/dashboard/api/helpCenter/portals.js @@ -6,6 +6,10 @@ class PortalsAPI extends ApiClient { super('portals', { accountScoped: true }); } + getPortal({ portalSlug, locale }) { + return axios.get(`${this.url}/${portalSlug}?locale=${locale}`); + } + updatePortal({ portalSlug, portalObj }) { return axios.patch(`${this.url}/${portalSlug}`, portalObj); } diff --git a/app/javascript/dashboard/components/index.js b/app/javascript/dashboard/components/index.js index 5e58aa049..f5ce5d856 100644 --- a/app/javascript/dashboard/components/index.js +++ b/app/javascript/dashboard/components/index.js @@ -5,9 +5,12 @@ import Button from './ui/WootButton'; import Code from './Code'; import ColorPicker from './widgets/ColorPicker'; import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue'; +import ConfirmModal from './widgets/modal/ConfirmationModal.vue'; +import ContextMenu from './ui/ContextMenu.vue'; import DeleteModal from './widgets/modal/DeleteModal.vue'; import DropdownItem from 'shared/components/ui/dropdown/DropdownItem'; import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu'; +import FeatureToggle from './widgets/FeatureToggle'; import HorizontalBar from './widgets/chart/HorizontalBarChart'; import Input from './widgets/forms/Input.vue'; import Label from './ui/Label'; @@ -21,8 +24,6 @@ import SubmitButton from './buttons/FormSubmitButton'; import Tabs from './ui/Tabs/Tabs'; import TabsItem from './ui/Tabs/TabsItem'; import Thumbnail from './widgets/Thumbnail.vue'; -import ConfirmModal from './widgets/modal/ConfirmationModal.vue'; -import ContextMenu from './ui/ContextMenu.vue'; const WootUIKit = { AvatarUploader, @@ -31,9 +32,12 @@ const WootUIKit = { Code, ColorPicker, ConfirmDeleteModal, + ConfirmModal, + ContextMenu, DeleteModal, DropdownItem, DropdownMenu, + FeatureToggle, HorizontalBar, Input, Label, @@ -47,8 +51,6 @@ const WootUIKit = { Tabs, TabsItem, Thumbnail, - ConfirmModal, - ContextMenu, install(Vue) { const keys = Object.keys(this); keys.pop(); // remove 'install' from keys diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryChildNavItem.vue b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryChildNavItem.vue index de10dda4a..2dd23544f 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryChildNavItem.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryChildNavItem.vue @@ -26,7 +26,7 @@ :class="{ 'text-truncate': shouldTruncate }" > {{ label }} - + {{ childItemCount }} @@ -76,7 +76,7 @@ export default { type: String, default: '', }, - isHelpCenterSidebar: { + showChildCount: { type: Boolean, default: false, }, @@ -127,11 +127,16 @@ $label-badge-size: var(--space-slab); color: var(--w-500); border-color: var(--w-25); } + &.is-active .count-view { + background: var(--w-75); + color: var(--w-500); + } } .menu-label { flex-grow: 1; - line-height: var(--space-two); + display: inline-flex; + align-items: center; } .inbox-icon { @@ -175,10 +180,6 @@ $label-badge-size: var(--space-slab); font-weight: var(--font-weight-bold); margin-left: var(--space-smaller); padding: var(--space-zero) var(--space-smaller); - - &.is-active { - background: var(--w-50); - color: var(--w-500); - } + line-height: var(--font-size-small); } diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue index 8661a488f..644258f50 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue @@ -4,16 +4,15 @@ {{ $t(`SIDEBAR.${menuItem.label}`) }} - {{ $t(`SIDEBAR.${menuItem.label}`) }} - + {{ `${menuItem.count}` }} -
  • +
  • -

    - {{ $t('SIDEBAR.HELP_CENTER.CATEGORY_EMPTY_MESSAGE') }} -

    @@ -104,14 +96,6 @@ export default { type: Object, default: () => ({}), }, - isHelpCenterSidebar: { - type: Boolean, - default: false, - }, - isCategoryEmpty: { - type: Boolean, - default: false, - }, }, computed: { ...mapGetters({ @@ -161,8 +145,8 @@ export default { this.menuItem.toStateName === 'settings_applications' ); }, - isArticlesView() { - return this.$store.state.route.name === this.menuItem.toStateName; + isCurrentRoute() { + return this.$store.state.route.name.includes(this.menuItem.toStateName); }, computedClass() { @@ -181,12 +165,11 @@ export default { } return ' '; } - if (this.isHelpCenterSidebar) { - if (this.isArticlesView) { - return 'is-active'; - } - return ' '; + + if (this.isCurrentRoute) { + return 'is-active'; } + return ''; }, }, @@ -222,6 +205,9 @@ export default { onClickOpen() { this.$emit('open'); }, + showChildCount(count) { + return Number.isInteger(count); + }, }, }; @@ -277,6 +263,11 @@ export default { color: var(--w-500); border-color: var(--w-25); } + + &.is-active .count-view { + background: var(--w-75); + color: var(--w-600); + } } .secondary-menu--icon { @@ -306,15 +297,12 @@ export default { top: -1px; } -.sidebar-item .button.menu-item--new { - display: inline-flex; - height: var(--space-medium); - margin: var(--space-smaller) 0; - padding: var(--space-smaller); - color: var(--s-500); +.sidebar-item .menu-item--new { + padding: var(--space-small) 0; - &:hover { - color: var(--w-500); + .button { + display: inline-flex; + color: var(--s-500); } } @@ -340,11 +328,6 @@ export default { font-weight: var(--font-weight-bold); margin-left: var(--space-smaller); padding: var(--space-zero) var(--space-smaller); - - &.is-active { - background: var(--w-50); - color: var(--w-500); - } } .submenu-icons { @@ -356,10 +339,4 @@ export default { margin-left: var(--space-small); } } - -.empty-text { - color: var(--s-500); - font-size: var(--font-size-small); - margin: var(--space-smaller); -} diff --git a/app/javascript/dashboard/components/ui/TimeAgo.vue b/app/javascript/dashboard/components/ui/TimeAgo.vue index c262a8aa0..c796ed0f6 100644 --- a/app/javascript/dashboard/components/ui/TimeAgo.vue +++ b/app/javascript/dashboard/components/ui/TimeAgo.vue @@ -1,17 +1,15 @@ diff --git a/app/javascript/dashboard/components/widgets/TableFooter.vue b/app/javascript/dashboard/components/widgets/TableFooter.vue index c89b37b01..e28099bff 100644 --- a/app/javascript/dashboard/components/widgets/TableFooter.vue +++ b/app/javascript/dashboard/components/widgets/TableFooter.vue @@ -83,75 +83,71 @@ export default { }, pageSize: { type: Number, - default: 15, + default: 25, }, totalCount: { type: Number, default: 0, }, - onPageChange: { - type: Function, - default: () => {}, - }, }, computed: { isFooterVisible() { return this.totalCount && !(this.firstIndex > this.totalCount); }, firstIndex() { - const firstIndex = this.pageSize * (this.currentPage - 1) + 1; - return firstIndex; + return this.pageSize * (this.currentPage - 1) + 1; }, lastIndex() { - const index = Math.min(this.totalCount, this.pageSize * this.currentPage); - return index; + return Math.min(this.totalCount, this.pageSize * this.currentPage); }, searchButtonClass() { return this.searchQuery !== '' ? 'show' : ''; }, hasLastPage() { - const isDisabled = - this.currentPage === Math.ceil(this.totalCount / this.pageSize); - return isDisabled; + return !!Math.ceil(this.totalCount / this.pageSize); }, hasFirstPage() { - const isDisabled = this.currentPage === 1; - return isDisabled; + return this.currentPage === 1; }, hasNextPage() { - const isDisabled = - this.currentPage === Math.ceil(this.totalCount / this.pageSize); - return isDisabled; + return this.currentPage === Math.ceil(this.totalCount / this.pageSize); }, hasPrevPage() { - const isDisabled = this.currentPage === 1; - return isDisabled; + return this.currentPage === 1; }, }, methods: { onNextPage() { - if (this.hasNextPage) return; + if (this.hasNextPage) { + return; + } const newPage = this.currentPage + 1; this.onPageChange(newPage); }, onPrevPage() { - if (this.hasPrevPage) return; - + if (this.hasPrevPage) { + return; + } const newPage = this.currentPage - 1; this.onPageChange(newPage); }, onFirstPage() { - if (this.hasFirstPage) return; - + if (this.hasFirstPage) { + return; + } const newPage = 1; this.onPageChange(newPage); }, onLastPage() { - if (this.hasLastPage) return; - + if (this.hasLastPage) { + return; + } const newPage = Math.ceil(this.totalCount / this.pageSize); this.onPageChange(newPage); }, + onPageChange(page) { + this.$emit('page-change', page); + }, }, }; diff --git a/app/javascript/dashboard/components/widgets/Thumbnail.vue b/app/javascript/dashboard/components/widgets/Thumbnail.vue index 4bfc729de..e0d9da24c 100644 --- a/app/javascript/dashboard/components/widgets/Thumbnail.vue +++ b/app/javascript/dashboard/components/widgets/Thumbnail.vue @@ -1,74 +1,23 @@ @@ -60,12 +60,12 @@ export default { }, pageSize: { type: Number, - default: 15, + default: 25, }, }, methods: { onPageChange(page) { - this.$emit('on-page-change', page); + this.$emit('page-change', page); }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue index 562d6bd07..b4626e108 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/HelpCenterLayout.vue @@ -2,6 +2,7 @@
    + ({ id: category.id, @@ -216,6 +225,13 @@ export default { return this.selectedPortal ? this.selectedPortal.name : ''; }, }, + + watch: { + '$route.params.portalSlug'() { + this.fetchPortalsAndItsCategories(); + }, + }, + mounted() { window.addEventListener('resize', this.handleResize); this.handleResize(); @@ -232,7 +248,7 @@ export default { }, updated() { const slug = this.$route.params.portalSlug; - if (slug) { + if (slug !== this.lastActivePortalSlug) { this.lastActivePortalSlug = slug; this.updateUISettings({ last_active_portal_slug: slug, @@ -251,12 +267,14 @@ export default { toggleSidebar() { this.isSidebarOpen = !this.isSidebarOpen; }, - fetchPortalsAndItsCategories() { - this.$store.dispatch('portals/index').then(() => { - this.$store.dispatch('categories/index', { - portalSlug: this.selectedPortalSlug, - }); - }); + async fetchPortalsAndItsCategories() { + await this.$store.dispatch('portals/index'); + const selectedPortalParam = { + portalSlug: this.selectedPortalSlug, + locale: this.selectedLocaleInPortal, + }; + this.$store.dispatch('portals/show', selectedPortalParam); + this.$store.dispatch('categories/index', selectedPortalParam); this.$store.dispatch('agents/get'); }, toggleKeyShortcutModal() { @@ -277,12 +295,15 @@ export default { closePortalPopover() { this.showPortalPopover = false; }, - onClickOpenAddCatogoryModal() { + onClickOpenAddCategoryModal() { this.showAddCategoryModal = true; }, onClickCloseAddCategoryModal() { this.showAddCategoryModal = false; }, + toggleAccountModal() { + this.showAccountModal = !this.showAccountModal; + }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalListItem.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalListItem.vue index 0354ad22b..045bd77cd 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalListItem.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalListItem.vue @@ -376,7 +376,6 @@ export default { } } .portal-title { - color: var(--s-900); margin-bottom: 0; } .portal-count { @@ -389,14 +388,17 @@ export default { } } .portal-locales { - margin-top: var(--space-medium); - margin-bottom: var(--space-small); + margin-bottom: var(--space-large); .locale-title { color: var(--s-800); font-weight: var(--font-weight-medium); margin-bottom: var(--space-small); } } + + .portal--heading { + margin-bottom: var(--space-normal); + } } .portal-settings--icon { padding: var(--space-smaller); diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue index 91b8a0467..4404a6c67 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/PortalSwitch.vue @@ -112,16 +112,8 @@ export default { this.selectedLocale = this.locale || this.portal?.meta?.default_locale; }, methods: { - fetchPortalsAndItsCategories() { - this.$store.dispatch('portals/index').then(() => { - this.$store.dispatch('categories/index', { - portalSlug: this.portal.slug, - }); - }); - }, onClick(event, code, portal) { event.preventDefault(); - this.fetchPortalsAndItsCategories(); this.$router.push({ name: 'list_all_locale_articles', params: { 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 355ffc0a1..a88121eb3 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/Sidebar.vue @@ -12,16 +12,20 @@ v-for="menuItem in accessibleMenuItems" :key="menuItem.toState" :menu-item="menuItem" - :is-help-center-sidebar="true" /> +

    + {{ $t('SIDEBAR.HELP_CENTER.CATEGORY_EMPTY_MESSAGE') }} +

    @@ -123,4 +127,8 @@ export default { overflow-y: auto; } } + +.empty-text { + padding: var(--space-smaller) var(--space-normal); +} diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/SidebarHeader.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/SidebarHeader.vue index 044efafd6..d526787bf 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/SidebarHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/Sidebar/SidebarHeader.vue @@ -76,7 +76,7 @@ export default { justify-content: space-between; padding: var(--space-normal); margin: var(--space-minus-small); - margin-bottom: var(--space-small); + margin-bottom: var(--space-smaller); border-bottom: 1px solid var(--color-border-light); } diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/AddCategory.stories.js b/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/AddCategory.stories.js index c0b5bb079..c0d2a7507 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/AddCategory.stories.js +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/AddCategory.stories.js @@ -1,4 +1,4 @@ -import AddCategoryComponent from '../AddCategory.vue'; +import AddCategoryComponent from '../../pages/categories/AddCategory.vue'; import { action } from '@storybook/addon-actions'; export default { diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/ArticleEditor.stories.js b/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/ArticleEditor.stories.js index a790350a7..1f507fbad 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/ArticleEditor.stories.js +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/stories/ArticleEditor.stories.js @@ -1,5 +1,5 @@ import { action } from '@storybook/addon-actions'; -import ArticleEditor from './ArticleEditor.vue'; +import ArticleEditor from '../../components/ArticleEditor.vue'; export default { title: 'Components/Help Center', diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/DefaultPortalArticles.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/DefaultPortalArticles.vue index a9033b6ac..6381ebe7b 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/DefaultPortalArticles.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/DefaultPortalArticles.vue @@ -13,7 +13,7 @@ export default { } = this.uiSettings || {}; if (lastActivePortalSlug) - this.$router.push({ + this.$router.replace({ name: 'list_all_locale_articles', params: { portalSlug: lastActivePortalSlug, @@ -22,7 +22,7 @@ export default { replace: true, }); else - this.$router.push({ + this.$router.replace({ name: 'list_all_portals', replace: true, }); diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ListAllArticles.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ListAllArticles.vue index 628db3782..cacbd2195 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ListAllArticles.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ListAllArticles.vue @@ -2,16 +2,15 @@
    @@ -105,9 +104,6 @@ export default { } return null; }, - articleCount() { - return this.articles ? this.articles.length : 0; - }, headerTitleInCategoryView() { return this.categories && this.categories.length ? this.selectedCategory.name @@ -128,9 +124,9 @@ export default { newArticlePage() { this.$router.push({ name: 'new_article' }); }, - fetchArticles() { + fetchArticles({ pageNumber } = {}) { this.$store.dispatch('articles/index', { - pageNumber: this.pageNumber, + pageNumber: pageNumber || this.pageNumber, portalSlug: this.$route.params.portalSlug, locale: this.$route.params.locale, status: this.status, @@ -138,8 +134,8 @@ export default { category_slug: this.selectedCategorySlug, }); }, - onPageChange(page) { - this.fetchArticles({ pageNumber: page }); + onPageChange(pageNumber) { + this.fetchArticles({ pageNumber }); }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalDetails.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalDetails.vue index 78876f630..385eb44ca 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalDetails.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/PortalDetails.vue @@ -6,7 +6,7 @@ 'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.CREATE_BASIC_SETTING_BUTTON' ) " - @submit="updateBasicSettings" + @submit="createPortal" /> @@ -37,7 +37,7 @@ export default { }, methods: { - async updateBasicSettings(portal) { + async createPortal(portal) { try { await this.$store.dispatch('portals/create', { portal, @@ -45,16 +45,16 @@ export default { this.alertMessage = this.$t( 'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_BASIC' ); + this.$router.push({ + name: 'portal_customization', + params: { portalSlug: portal.slug }, + }); } catch (error) { this.alertMessage = error?.message || this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_BASIC'); } finally { this.showAlert(this.alertMessage); - this.$router.push({ - name: 'portal_customization', - params: { portalSlug: portal.slug }, - }); } }, }, diff --git a/app/javascript/dashboard/routes/dashboard/notifications/components/NotificationsView.vue b/app/javascript/dashboard/routes/dashboard/notifications/components/NotificationsView.vue index 587a8bb54..ca60b3750 100644 --- a/app/javascript/dashboard/routes/dashboard/notifications/components/NotificationsView.vue +++ b/app/javascript/dashboard/routes/dashboard/notifications/components/NotificationsView.vue @@ -9,9 +9,9 @@ :on-mark-all-done-click="onMarkAllDoneClick" />
    diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue index a6feb1346..248aa884b 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/Index.vue @@ -225,7 +225,7 @@ export default { mode === 'EDIT' ? this.$t('AUTOMATION.EDIT.API.SUCCESS_MESSAGE') : this.$t('AUTOMATION.ADD.API.SUCCESS_MESSAGE'); - await await this.$store.dispatch(action, payload); + await this.$store.dispatch(action, payload); this.showAlert(this.$t(successMessage)); this.hideAddPopup(); this.hideEditPopup(); diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/ActionButton.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/ActionButton.vue new file mode 100644 index 000000000..ce5645961 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/ActionButton.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue index 39f017fef..31f661a55 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/Index.vue @@ -1,11 +1,121 @@ - + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue index 2de1f787c..6a6531f88 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue @@ -1,9 +1,145 @@ - + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue new file mode 100644 index 000000000..95db0ed65 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroNode.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroNode.vue new file mode 100644 index 000000000..533512c87 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroNode.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroNodes.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroNodes.vue new file mode 100644 index 000000000..0123055cf --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroNodes.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroProperties.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroProperties.vue new file mode 100644 index 000000000..076dad07b --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroProperties.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacrosTableRow.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacrosTableRow.vue new file mode 100644 index 000000000..29419db85 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacrosTableRow.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/Pill.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/Pill.vue new file mode 100644 index 000000000..d17afe774 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/Pill.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/constants.js b/app/javascript/dashboard/routes/dashboard/settings/macros/constants.js new file mode 100644 index 000000000..d0638d414 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/constants.js @@ -0,0 +1,42 @@ +export const MACRO_ACTION_TYPES = [ + { + key: 'assign_team', + label: 'Assign a team', + inputType: 'multi_select', + }, + { + key: 'add_label', + label: 'Add a label', + inputType: 'multi_select', + }, + { + key: 'send_email_transcript', + label: 'Send an email transcript', + inputType: 'email', + }, + { + key: 'mute_conversation', + label: 'Mute conversation', + inputType: null, + }, + { + key: 'snooze_conversation', + label: 'Snooze conversation', + inputType: null, + }, + { + key: 'resolve_conversation', + label: 'Resolve conversation', + inputType: null, + }, + { + key: 'send_attachment', + label: 'Send Attachment', + inputType: 'attachment', + }, + { + key: 'send_message', + label: 'Send a message', + inputType: 'textarea', + }, +]; diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macroHelper.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macroHelper.js new file mode 100644 index 000000000..584deeb5e --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macroHelper.js @@ -0,0 +1,33 @@ +import { MACRO_ACTION_TYPES as macroActionTypes } from 'dashboard/routes/dashboard/settings/macros/constants.js'; +export const emptyMacro = { + name: '', + actions: [ + { + action_name: 'assign_team', + action_params: [], + }, + ], + visibility: 'global', +}; + +export const resolveActionName = key => { + return macroActionTypes.find(i => i.key === key).label; +}; + +export const resolveTeamIds = (teams, ids) => { + return ids + .map(id => { + const team = teams.find(i => i.id === id); + return team ? team.name : ''; + }) + .join(', '); +}; + +export const resolveLabels = (labels, ids) => { + return ids + .map(id => { + const label = labels.find(i => i.title === id); + return label ? label.title : ''; + }) + .join(', '); +}; diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js index 9752b79fb..23306a8bf 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js @@ -8,10 +8,14 @@ export default { { path: frontendURL('accounts/:accountId/settings/macros'), component: SettingsContent, - props: { - headerTitle: 'MACROS.HEADER', - icon: 'flash-settings', - showNewButton: false, + props: params => { + const showBackButton = params.name !== 'macros_wrapper'; + return { + headerTitle: 'MACROS.HEADER', + headerButtonText: 'MACROS.HEADER_BTN_TXT', + icon: 'flash-settings', + showBackButton, + }; }, children: [ { diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index f2fe95b50..481c6f107 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -2,14 +2,16 @@ import Vue from 'vue'; import Vuex from 'vuex'; import accounts from './modules/accounts'; -import agents from './modules/agents'; import agentBots from './modules/agentBots'; +import agents from './modules/agents'; +import articles from './modules/helpCenterArticles'; import attributes from './modules/attributes'; import auth from './modules/auth'; import automations from './modules/automations'; import bulkActions from './modules/bulkActions'; import campaigns from './modules/campaigns'; import cannedResponse from './modules/cannedResponse'; +import categories from './modules/helpCenterCategories'; import contactConversations from './modules/contactConversations'; import contactLabels from './modules/contactLabels'; import contactNotes from './modules/contactNotes'; @@ -30,28 +32,29 @@ import inboxes from './modules/inboxes'; import inboxMembers from './modules/inboxMembers'; import integrations from './modules/integrations'; import labels from './modules/labels'; +import macros from './modules/macros'; import notifications from './modules/notifications'; +import portals from './modules/helpCenterPortals'; import reports from './modules/reports'; import teamMembers from './modules/teamMembers'; import teams from './modules/teams'; import userNotificationSettings from './modules/userNotificationSettings'; import webhooks from './modules/webhooks'; -import articles from './modules/helpCenterArticles'; -import portals from './modules/helpCenterPortals'; -import categories from './modules/helpCenterCategories'; Vue.use(Vuex); export default new Vuex.Store({ modules: { accounts, - agents, agentBots, + agents, + articles, attributes, auth, automations, bulkActions, campaigns, cannedResponse, + categories, contactConversations, contactLabels, contactNotes, @@ -72,14 +75,13 @@ export default new Vuex.Store({ inboxMembers, integrations, labels, + macros, notifications, + portals, reports, teamMembers, teams, userNotificationSettings, webhooks, - articles, - portals, - categories, }, }); diff --git a/app/javascript/dashboard/store/modules/conversations/actions.js b/app/javascript/dashboard/store/modules/conversations/actions.js index c00ae4409..a8195cbbb 100644 --- a/app/javascript/dashboard/store/modules/conversations/actions.js +++ b/app/javascript/dashboard/store/modules/conversations/actions.js @@ -252,6 +252,12 @@ const actions = { meta: { sender }, } = conversation; commit(types.UPDATE_CONVERSATION, conversation); + + dispatch('conversationLabels/setConversationLabel', { + id: conversation.id, + data: conversation.labels, + }); + dispatch('contacts/setContact', sender); }, diff --git a/app/javascript/dashboard/store/modules/helpCenterCategories/actions.js b/app/javascript/dashboard/store/modules/helpCenterCategories/actions.js index ebea5d444..2d212ded3 100644 --- a/app/javascript/dashboard/store/modules/helpCenterCategories/actions.js +++ b/app/javascript/dashboard/store/modules/helpCenterCategories/actions.js @@ -2,13 +2,13 @@ import categoriesAPI from 'dashboard/api/helpCenter/categories.js'; import { throwErrorMessage } from 'dashboard/store/utils/api'; import types from '../../mutation-types'; export const actions = { - index: async ({ commit }, { portalSlug }) => { + index: async ({ commit }, { portalSlug, locale }) => { try { commit(types.SET_UI_FLAG, { isFetching: true }); if (portalSlug) { const { data: { payload }, - } = await categoriesAPI.get({ portalSlug }); + } = await categoriesAPI.get({ portalSlug, locale }); commit(types.CLEAR_CATEGORIES); const categoryIds = payload.map(category => category.id); commit(types.ADD_MANY_CATEGORIES, payload); diff --git a/app/javascript/dashboard/store/modules/helpCenterPortals/actions.js b/app/javascript/dashboard/store/modules/helpCenterPortals/actions.js index 2be7dc03f..bfae85d27 100644 --- a/app/javascript/dashboard/store/modules/helpCenterPortals/actions.js +++ b/app/javascript/dashboard/store/modules/helpCenterPortals/actions.js @@ -7,14 +7,12 @@ export const actions = { try { commit(types.SET_UI_FLAG, { isFetching: true }); const { - data: { payload, meta }, + data: { payload }, } = await portalAPIs.get(); commit(types.CLEAR_PORTALS); const portalSlugs = payload.map(portal => portal.slug); commit(types.ADD_MANY_PORTALS_ENTRY, payload); commit(types.ADD_MANY_PORTALS_IDS, portalSlugs); - - commit(types.SET_PORTALS_META, meta); } catch (error) { throwErrorMessage(error); } finally { @@ -22,6 +20,21 @@ export const actions = { } }, + show: async ({ commit }, { portalSlug, locale }) => { + commit(types.SET_UI_FLAG, { isFetchingItem: true }); + try { + const response = await portalAPIs.getPortal({ portalSlug, locale }); + const { + data: { meta }, + } = response; + commit(types.SET_PORTALS_META, meta); + } catch (error) { + // Ignore error + } finally { + commit(types.SET_UI_FLAG, { isFetchingItem: false }); + } + }, + create: async ({ commit }, params) => { commit(types.SET_UI_FLAG, { isCreating: true }); try { diff --git a/app/javascript/dashboard/store/modules/helpCenterPortals/getters.js b/app/javascript/dashboard/store/modules/helpCenterPortals/getters.js index c3cc790aa..b52f6b4ad 100644 --- a/app/javascript/dashboard/store/modules/helpCenterPortals/getters.js +++ b/app/javascript/dashboard/store/modules/helpCenterPortals/getters.js @@ -20,7 +20,5 @@ export const getters = { return portals; }, count: state => state.portals.allIds.length || 0, - getMeta: state => { - return state.meta; - }, + getMeta: state => state.meta, }; diff --git a/app/javascript/dashboard/store/modules/helpCenterPortals/index.js b/app/javascript/dashboard/store/modules/helpCenterPortals/index.js index fecb17d4d..346eb9e33 100755 --- a/app/javascript/dashboard/store/modules/helpCenterPortals/index.js +++ b/app/javascript/dashboard/store/modules/helpCenterPortals/index.js @@ -10,8 +10,10 @@ export const defaultPortalFlags = { const state = { meta: { - count: 0, - currentPage: 1, + allArticlesCount: 0, + mineArticlesCount: 0, + draftArticlesCount: 0, + archivedArticlesCount: 0, }, portals: { diff --git a/app/javascript/dashboard/store/modules/helpCenterPortals/mutations.js b/app/javascript/dashboard/store/modules/helpCenterPortals/mutations.js index 421b0dc7e..9ece174cd 100644 --- a/app/javascript/dashboard/store/modules/helpCenterPortals/mutations.js +++ b/app/javascript/dashboard/store/modules/helpCenterPortals/mutations.js @@ -44,9 +44,16 @@ export const mutations = { }, [types.SET_PORTALS_META]: ($state, data) => { - const { portals_count: count, current_page: currentPage } = data; - Vue.set($state.meta, 'count', count); - Vue.set($state.meta, 'currentPage', currentPage); + const { + all_articles_count: allArticlesCount = 0, + mine_articles_count: mineArticlesCount = 0, + draft_articles_count: draftArticlesCount = 0, + archived_articles_count: archivedArticlesCount = 0, + } = data; + Vue.set($state.meta, 'allArticlesCount', allArticlesCount); + Vue.set($state.meta, 'archivedArticlesCount', archivedArticlesCount); + Vue.set($state.meta, 'mineArticlesCount', mineArticlesCount); + Vue.set($state.meta, 'draftArticlesCount', draftArticlesCount); }, [types.ADD_PORTAL_ID]($state, portalSlug) { diff --git a/app/javascript/dashboard/store/modules/helpCenterPortals/specs/actions.spec.js b/app/javascript/dashboard/store/modules/helpCenterPortals/specs/actions.spec.js index aeda57df0..6fdd054b3 100644 --- a/app/javascript/dashboard/store/modules/helpCenterPortals/specs/actions.spec.js +++ b/app/javascript/dashboard/store/modules/helpCenterPortals/specs/actions.spec.js @@ -22,7 +22,6 @@ describe('#actions', () => { [types.CLEAR_PORTALS], [types.ADD_MANY_PORTALS_ENTRY, apiResponse.payload], [types.ADD_MANY_PORTALS_IDS, ['domain', 'campaign']], - [types.SET_PORTALS_META, { current_page: 1, portals_count: 1 }], [types.SET_UI_FLAG, { isFetching: false }], ]); }); @@ -66,6 +65,36 @@ describe('#actions', () => { }); }); + describe('#show', () => { + it('sends correct actions if API is success', async () => { + axios.get.mockResolvedValue({ + data: { meta: { all_articles_count: 1 } }, + }); + await actions.show( + { commit }, + { + portalSlug: 'handbook', + locale: 'en', + } + ); + expect(commit.mock.calls).toEqual([ + [types.SET_UI_FLAG, { isFetchingItem: true }], + [types.SET_PORTALS_META, { all_articles_count: 1 }], + [types.SET_UI_FLAG, { isFetchingItem: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.post.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.create({ commit, dispatch, state: { portals: {} } }, {}) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_UI_FLAG, { isCreating: true }], + [types.SET_UI_FLAG, { isCreating: false }], + ]); + }); + }); + describe('#update', () => { it('sends correct actions if API is success', async () => { axios.patch.mockResolvedValue({ data: apiResponse.payload[1] }); diff --git a/app/javascript/dashboard/store/modules/helpCenterPortals/specs/mutations.spec.js b/app/javascript/dashboard/store/modules/helpCenterPortals/specs/mutations.spec.js index d20cacd1b..2e58c1de7 100644 --- a/app/javascript/dashboard/store/modules/helpCenterPortals/specs/mutations.spec.js +++ b/app/javascript/dashboard/store/modules/helpCenterPortals/specs/mutations.spec.js @@ -107,12 +107,20 @@ describe('#mutations', () => { describe('#SET_PORTALS_META', () => { it('add meta to state', () => { mutations[types.SET_PORTALS_META](state, { - portals_count: 10, - current_page: 1, - }); - expect(state.meta).toEqual({ count: 10, currentPage: 1, + all_articles_count: 10, + archived_articles_count: 10, + draft_articles_count: 10, + mine_articles_count: 10, + }); + expect(state.meta).toEqual({ + count: 0, + currentPage: 1, + allArticlesCount: 10, + archivedArticlesCount: 10, + draftArticlesCount: 10, + mineArticlesCount: 10, }); }); }); diff --git a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js index d566b1df7..9259a3972 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js @@ -58,6 +58,7 @@ describe('#actions', () => { id: 1, messages: [], meta: { sender: { id: 1, name: 'john-doe' } }, + labels: ['support'], }; actions.updateConversation( { commit, rootState: { route: { name: 'home' } }, dispatch }, @@ -67,6 +68,10 @@ describe('#actions', () => { [types.UPDATE_CONVERSATION, conversation], ]); expect(dispatch.mock.calls).toEqual([ + [ + 'conversationLabels/setConversationLabel', + { id: 1, data: ['support'] }, + ], [ 'contacts/setContact', { diff --git a/app/javascript/packs/portal.js b/app/javascript/packs/portal.js index 2f7736deb..5c1867658 100644 --- a/app/javascript/packs/portal.js +++ b/app/javascript/packs/portal.js @@ -3,8 +3,10 @@ // a relevant structure within app/javascript and only use these pack files to reference // that code so that it will be compiled. +import Vue from 'vue'; import Rails from '@rails/ujs'; import Turbolinks from 'turbolinks'; +import PublicArticleSearch from '../portal/components/PublicArticleSearch.vue'; import { navigateToLocalePage } from '../portal/portalHelpers'; @@ -13,4 +15,21 @@ import '../portal/application.scss'; Rails.start(); Turbolinks.start(); -document.addEventListener('DOMContentLoaded', navigateToLocalePage); +const initPageSetUp = () => { + navigateToLocalePage(); + const isSearchContainerAvailable = document.querySelector('#search-wrap'); + if (isSearchContainerAvailable) { + new Vue({ + components: { PublicArticleSearch }, + template: '', + }).$mount('#search-wrap'); + } +}; + +document.addEventListener('DOMContentLoaded', () => { + initPageSetUp(); +}); + +document.addEventListener('turbolinks:load', () => { + initPageSetUp(); +}); diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js index 2fdd6c57b..13e41a98e 100755 --- a/app/javascript/packs/sdk.js +++ b/app/javascript/packs/sdk.js @@ -18,6 +18,11 @@ const runSDK = ({ baseUrl, websiteToken }) => { } const chatwootSettings = window.chatwootSettings || {}; + let locale = chatwootSettings.locale || 'en'; + if (chatwootSettings.useBrowserLanguage) { + locale = window.navigator.language.replace('-', '_'); + } + window.$chatwoot = { baseUrl, hasLoaded: false, @@ -25,7 +30,8 @@ const runSDK = ({ baseUrl, websiteToken }) => { isOpen: false, position: chatwootSettings.position === 'left' ? 'left' : 'right', websiteToken, - locale: chatwootSettings.locale, + locale, + useBrowserLanguage: chatwootSettings.useBrowserLanguage || false, type: getBubbleView(chatwootSettings.type), launcherTitle: chatwootSettings.launcherTitle || '', showPopoutButton: chatwootSettings.showPopoutButton || false, @@ -58,7 +64,7 @@ const runSDK = ({ baseUrl, websiteToken }) => { IFrameHelper.events.popoutChatWindow({ baseUrl: window.$chatwoot.baseUrl, websiteToken: window.$chatwoot.websiteToken, - locale: window.$chatwoot.locale, + locale, }); }, diff --git a/app/javascript/portal/api/article.js b/app/javascript/portal/api/article.js new file mode 100644 index 000000000..d28f53c88 --- /dev/null +++ b/app/javascript/portal/api/article.js @@ -0,0 +1,14 @@ +import axios from 'axios'; + +class ArticlesAPI { + constructor() { + this.baseUrl = ''; + } + + searchArticles(portalSlug, locale, query) { + let baseUrl = `${this.baseUrl}/hc/${portalSlug}/${locale}/articles.json?query=${query}`; + return axios.get(baseUrl); + } +} + +export default new ArticlesAPI(); diff --git a/app/javascript/portal/components/PublicArticleSearch.vue b/app/javascript/portal/components/PublicArticleSearch.vue new file mode 100644 index 000000000..4ced0665b --- /dev/null +++ b/app/javascript/portal/components/PublicArticleSearch.vue @@ -0,0 +1,129 @@ + + + + diff --git a/app/javascript/portal/components/PublicSearchInput.vue b/app/javascript/portal/components/PublicSearchInput.vue new file mode 100644 index 000000000..5183f3293 --- /dev/null +++ b/app/javascript/portal/components/PublicSearchInput.vue @@ -0,0 +1,60 @@ + + + diff --git a/app/javascript/portal/components/SearchSuggestions.vue b/app/javascript/portal/components/SearchSuggestions.vue new file mode 100644 index 000000000..838d4ba7e --- /dev/null +++ b/app/javascript/portal/components/SearchSuggestions.vue @@ -0,0 +1,103 @@ + + + diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js index d31d1b6ba..647059b73 100644 --- a/app/javascript/portal/portalHelpers.js +++ b/app/javascript/portal/portalHelpers.js @@ -1,8 +1,13 @@ export const navigateToLocalePage = () => { const allLocaleSwitcher = document.querySelector('.locale-switcher'); + if (!allLocaleSwitcher) { + return false; + } + const { portalSlug } = allLocaleSwitcher.dataset; allLocaleSwitcher.addEventListener('change', event => { window.location = `/hc/${portalSlug}/${event.target.value}/`; }); + return false; }; diff --git a/app/javascript/shared/assets/stylesheets/border-radius.scss b/app/javascript/shared/assets/stylesheets/border-radius.scss index 69a2a804a..76ea3c731 100644 --- a/app/javascript/shared/assets/stylesheets/border-radius.scss +++ b/app/javascript/shared/assets/stylesheets/border-radius.scss @@ -1,8 +1,8 @@ :root { - // border-radius - --border-radius-small: 0.3rem; - --border-radius-normal: 0.5rem; - --border-radius-medium: 0.7rem; - --border-radius-large: 0.9rem; - --border-radius-rounded: 50%; + --border-radius-small: 0.3rem; + --border-radius-normal: 0.5rem; + --border-radius-medium: 0.7rem; + --border-radius-large: 0.9rem; + --border-radius-full: 10rem; + --border-radius-rounded: 50%; } diff --git a/app/javascript/shared/assets/stylesheets/spacing.scss b/app/javascript/shared/assets/stylesheets/spacing.scss index c556c65b8..7ef494a7a 100644 --- a/app/javascript/shared/assets/stylesheets/spacing.scss +++ b/app/javascript/shared/assets/stylesheets/spacing.scss @@ -16,9 +16,10 @@ --space-jumbo: 6.4rem; --space-mega: 10rem; --space-giga: 24rem; - + --space-minus-micro: -0.2rem; --space-minus-smaller: -0.4rem; + --space-minus-half: -0.5rem; --space-minus-small: -0.8rem; --space-minus-one: -1rem; --space-minus-slab: -1.2rem; diff --git a/app/javascript/shared/components/CardButton.vue b/app/javascript/shared/components/CardButton.vue index d6d116ade..981c6f93d 100644 --- a/app/javascript/shared/components/CardButton.vue +++ b/app/javascript/shared/components/CardButton.vue @@ -4,6 +4,7 @@ :key="action.uri" class="action-button button" :href="action.uri" + :style="{ background: widgetColor, borderColor: widgetColor }" target="_blank" rel="noopener nofollow noreferrer" > @@ -13,13 +14,14 @@ v-else :key="action.payload" class="action-button button" + :style="{ borderColor: widgetColor, color: widgetColor }" @click="onClick" > {{ action.text }} - diff --git a/app/views/platform/api/v1/accounts/create.json.jbuilder b/app/views/platform/api/v1/accounts/create.json.jbuilder new file mode 100644 index 000000000..449293eb6 --- /dev/null +++ b/app/views/platform/api/v1/accounts/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'platform/api/v1/models/account', formats: [:json], resource: @resource diff --git a/app/views/platform/api/v1/accounts/show.json.jbuilder b/app/views/platform/api/v1/accounts/show.json.jbuilder new file mode 100644 index 000000000..449293eb6 --- /dev/null +++ b/app/views/platform/api/v1/accounts/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'platform/api/v1/models/account', formats: [:json], resource: @resource diff --git a/app/views/platform/api/v1/accounts/update.json.jbuilder b/app/views/platform/api/v1/accounts/update.json.jbuilder new file mode 100644 index 000000000..449293eb6 --- /dev/null +++ b/app/views/platform/api/v1/accounts/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'platform/api/v1/models/account', formats: [:json], resource: @resource diff --git a/app/views/platform/api/v1/models/_account.json.jbuilder b/app/views/platform/api/v1/models/_account.json.jbuilder new file mode 100644 index 000000000..9a22e6518 --- /dev/null +++ b/app/views/platform/api/v1/models/_account.json.jbuilder @@ -0,0 +1,9 @@ +json.id resource.id +json.name resource.name +json.locale resource.locale +json.domain resource.domain +json.support_email resource.support_email +json.enabled_features resource.enabled_features +json.custom_attributes resource.custom_attributes +json.limits resource.limits +json.status resource.status diff --git a/app/views/public/api/v1/models/_article.json.jbuilder b/app/views/public/api/v1/models/_article.json.jbuilder index 994c6c029..cacf48dff 100644 --- a/app/views/public/api/v1/models/_article.json.jbuilder +++ b/app/views/public/api/v1/models/_article.json.jbuilder @@ -9,7 +9,15 @@ json.last_updated_at article.updated_at if article.portal.present? json.portal do - json.partial! 'api/v1/accounts/portals/portal', formats: [:json], portal: article.portal + json.partial! 'public/api/v1/models/hc/portal', formats: [:json], portal: article.portal + end +end + +if article.category.present? + json.category do + json.id article.category.id + json.slug article.category.slug + json.locale article.category.locale end end @@ -17,14 +25,14 @@ json.views article.views if article.author.present? json.author do - json.partial! 'api/v1/models/agent', formats: [:json], resource: article.author + json.partial! 'public/api/v1/models/hc/author', formats: [:json], resource: article.author end end json.associated_articles do if article.associated_articles.any? json.array! article.associated_articles.each do |associated_article| - json.partial! 'api/v1/accounts/articles/associated_article', formats: [:json], article: associated_article + json.partial! 'public/api/v1/models/hc/associated_article', formats: [:json], article: associated_article end end end diff --git a/app/views/public/api/v1/models/_associated_article.json.jbuilder b/app/views/public/api/v1/models/hc/_associated_article.json.jbuilder similarity index 76% rename from app/views/public/api/v1/models/_associated_article.json.jbuilder rename to app/views/public/api/v1/models/hc/_associated_article.json.jbuilder index 178d67d24..dde37e5be 100644 --- a/app/views/public/api/v1/models/_associated_article.json.jbuilder +++ b/app/views/public/api/v1/models/hc/_associated_article.json.jbuilder @@ -10,6 +10,6 @@ json.views article.views if article.author.present? json.author do - json.partial! 'api/v1/models/agent', formats: [:json], resource: article.author + json.partial! 'public/api/v1/models/portal/author', formats: [:json], resource: article.author end end diff --git a/app/views/public/api/v1/models/hc/_author.json.jbuilder b/app/views/public/api/v1/models/hc/_author.json.jbuilder new file mode 100644 index 000000000..954a5f07c --- /dev/null +++ b/app/views/public/api/v1/models/hc/_author.json.jbuilder @@ -0,0 +1,3 @@ +json.available_name resource.available_name +json.name resource.name +json.thumbnail resource.avatar_url diff --git a/app/views/public/api/v1/models/_portal.json.jbuilder b/app/views/public/api/v1/models/hc/_portal.json.jbuilder similarity index 100% rename from app/views/public/api/v1/models/_portal.json.jbuilder rename to app/views/public/api/v1/models/hc/_portal.json.jbuilder diff --git a/app/views/public/api/v1/portals/_footer.html.erb b/app/views/public/api/v1/portals/_footer.html.erb index cd69a4fe4..2d0533b78 100644 --- a/app/views/public/api/v1/portals/_footer.html.erb +++ b/app/views/public/api/v1/portals/_footer.html.erb @@ -2,7 +2,7 @@ diff --git a/app/views/public/api/v1/portals/_header.html.erb b/app/views/public/api/v1/portals/_header.html.erb index b7b1cbad2..59065d012 100644 --- a/app/views/public/api/v1/portals/_header.html.erb +++ b/app/views/public/api/v1/portals/_header.html.erb @@ -22,7 +22,7 @@