diff --git a/.rubocop.yml b/.rubocop.yml index d63f0418d..dafd9a620 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -184,3 +184,4 @@ AllCops: - db/migrate/20200927135222_add_last_activity_at_to_conversation.rb - db/migrate/20210306170117_add_last_activity_at_to_contacts.rb - db/migrate/20220809104508_revert_cascading_indexes.rb + diff --git a/Gemfile b/Gemfile index ba83ce964..9c3590017 100644 --- a/Gemfile +++ b/Gemfile @@ -158,6 +158,10 @@ group :test do gem 'webmock' end +group :development, :test, :staging do + gem 'faker' +end + group :development, :test do gem 'active_record_query_trace' ##--- gems for debugging and error reporting ---## @@ -167,7 +171,6 @@ group :development, :test do gem 'byebug', platform: :mri gem 'climate_control' gem 'factory_bot_rails' - gem 'faker' gem 'listen' gem 'mock_redis' gem 'pry-rails' diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb index c3d8ad024..6ac000b58 100644 --- a/app/controllers/api/v1/accounts/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -58,7 +58,8 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController def portal_params params.require(:portal).permit( - :account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug, :archived, config: { allowed_locales: [] } + :account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug, :archived, { config: [:default_locale, + { allowed_locales: [] }] } ) end diff --git a/app/controllers/super_admin/accounts_controller.rb b/app/controllers/super_admin/accounts_controller.rb index 28cae96b0..3d038281c 100644 --- a/app/controllers/super_admin/accounts_controller.rb +++ b/app/controllers/super_admin/accounts_controller.rb @@ -41,4 +41,9 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController # See https://administrate-prototype.herokuapp.com/customizing_controller_actions # for more information + + def seed + Seeders::AccountSeeder.new(account: requested_resource).perform! + redirect_back(fallback_location: [namespace, requested_resource], notice: 'Account seeding triggered') + end end diff --git a/app/fields/avatar_field.rb b/app/fields/avatar_field.rb index a9674eb94..c443b7a21 100644 --- a/app/fields/avatar_field.rb +++ b/app/fields/avatar_field.rb @@ -2,6 +2,8 @@ require 'administrate/field/base' class AvatarField < Administrate::Field::Base def avatar_url - data.presence&.gsub('?d=404', '?d=mp') + return data.presence if data.presence + + resource.is_a?(User) ? '/assets/administrate/user/avatar.png' : '/assets/administrate/bot/avatar.png' end end diff --git a/app/javascript/dashboard/api/helpCenter/articles.js b/app/javascript/dashboard/api/helpCenter/articles.js index 8bc960267..62328e8eb 100644 --- a/app/javascript/dashboard/api/helpCenter/articles.js +++ b/app/javascript/dashboard/api/helpCenter/articles.js @@ -42,6 +42,10 @@ class ArticlesAPI extends PortalsAPI { category_id, }); } + + deleteArticle({ articleId, portalSlug }) { + return axios.delete(`${this.url}/${portalSlug}/articles/${articleId}`); + } } export default new ArticlesAPI(); diff --git a/app/javascript/dashboard/api/helpCenter/portals.js b/app/javascript/dashboard/api/helpCenter/portals.js index ec9528f73..a71f18f5c 100644 --- a/app/javascript/dashboard/api/helpCenter/portals.js +++ b/app/javascript/dashboard/api/helpCenter/portals.js @@ -9,6 +9,10 @@ class PortalsAPI extends ApiClient { updatePortal({ portalSlug, params }) { return axios.patch(`${this.url}/${portalSlug}`, params); } + + deletePortal(portalSlug) { + return axios.delete(`${this.url}/${portalSlug}`); + } } export default PortalsAPI; diff --git a/app/javascript/dashboard/api/specs/article.spec.js b/app/javascript/dashboard/api/specs/article.spec.js index e20e2e222..51e90318f 100644 --- a/app/javascript/dashboard/api/specs/article.spec.js +++ b/app/javascript/dashboard/api/specs/article.spec.js @@ -52,4 +52,15 @@ describe('#PortalAPI', () => { ); }); }); + describeWithAPIMock('API calls', context => { + it('#deleteArticle', () => { + articlesAPI.deleteArticle({ + articleId: 1, + portalSlug: 'room-rental', + }); + expect(context.axiosMock.delete).toHaveBeenCalledWith( + '/api/v1/portals/room-rental/articles/1' + ); + }); + }); }); diff --git a/app/javascript/dashboard/assets/scss/widgets/_modal.scss b/app/javascript/dashboard/assets/scss/widgets/_modal.scss index 68ea09589..fc497f069 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_modal.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_modal.scss @@ -85,7 +85,7 @@ .modal-footer { @include flex; - @include flex-align($x: flex-start, $y: middle); + @include flex-align($x: flex-end, $y: middle); padding: $space-small $zero; button { diff --git a/app/javascript/dashboard/components/ModalHeader.vue b/app/javascript/dashboard/components/ModalHeader.vue index 0f8d8931c..b60f5d851 100644 --- a/app/javascript/dashboard/components/ModalHeader.vue +++ b/app/javascript/dashboard/components/ModalHeader.vue @@ -6,6 +6,9 @@

{{ headerContent }} + + {{ headerContentValue }} +

@@ -22,6 +25,10 @@ export default { type: String, default: '', }, + headerContentValue: { + type: String, + default: '', + }, headerImage: { type: String, default: '', @@ -32,5 +39,8 @@ export default { diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue index 861805589..319aa5fc1 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue @@ -5,9 +5,10 @@ {{ $t(`SIDEBAR.${menuItem.label}`) }} @@ -32,7 +32,7 @@ {{ $t('HELP_CENTER.PORTAL.POPOVER.CANCEL_BUTTON_LABEL') }} - + {{ $t('HELP_CENTER.PORTAL.POPOVER.CHOOSE_LOCALE_BUTTON') }} @@ -52,19 +52,26 @@ export default { type: Array, default: () => [], }, - activePortal: { - type: Object, - default: () => ({}), + activePortalSlug: { + type: String, + default: '', }, }, + methods: { closePortalPopover() { this.$emit('close-popover'); }, - openPortalPage() { + openPortalArticles({ slug, locale }) { this.$emit('close-popover'); + const portal = this.portals.find(p => p.slug === slug); + this.$store.dispatch('portals/setPortalId', portal.id); this.$router.push({ - name: 'list_all_portals', + name: 'list_all_locale_articles', + params: { + portalSlug: slug, + locale: locale, + }, }); }, }, diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue index 6817c175b..5dc08c5cb 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ArticleSettings.vue @@ -127,7 +127,7 @@ export default { props: { article: { type: Object, - required: true, + default: () => ({}), }, }, data() { 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 0aefc7902..2d6402e62 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/EditArticle.vue @@ -27,6 +27,17 @@ v-if="showArticleSettings" :article="article" @save-article="saveArticle" + @delete-article="openDeletePopup" + @archive-article="archiveArticle" + /> + @@ -52,13 +63,13 @@ export default { isSaved: false, showArticleSettings: false, alertMessage: '', + showDeleteConfirmationPopup: false, }; }, computed: { ...mapGetters({ isFetching: 'articles/isFetching', articles: 'articles/articles', - selectedPortal: 'portals/getSelectedPortal', }), article() { return this.$store.getters['articles/articleById'](this.articleId); @@ -67,7 +78,7 @@ export default { return this.$route.params.articleSlug; }, selectedPortalSlug() { - return this.portalSlug || this.selectedPortal?.slug; + return this.$route.params.portalSlug; }, }, mounted() { @@ -83,6 +94,16 @@ export default { portalSlug: this.selectedPortalSlug, }); }, + openDeletePopup() { + this.showDeleteConfirmationPopup = true; + }, + closeDeletePopup() { + this.showDeleteConfirmationPopup = false; + }, + confirmDeletion() { + this.closeDeletePopup(); + this.deleteArticle(); + }, async saveArticle({ ...values }) { this.isUpdating = true; try { @@ -93,8 +114,7 @@ export default { }); } catch (error) { this.alertMessage = - error?.message || - this.$t('HELP_CENTER.EDIT_ARTICLE.API.ERROR_MESSAGE'); + error?.message || this.$t('HELP_CENTER.EDIT_ARTICLE.API.ERROR'); this.showAlert(this.alertMessage); } finally { setTimeout(() => { @@ -103,6 +123,45 @@ export default { }, 1500); } }, + async deleteArticle() { + try { + await this.$store.dispatch('articles/delete', { + portalSlug: this.selectedPortalSlug, + articleId: this.articleId, + }); + this.alertMessage = this.$t( + 'HELP_CENTER.DELETE_ARTICLE.API.SUCCESS_MESSAGE' + ); + this.$router.push({ + name: 'list_all_locale_articles', + params: { + portalSlug: this.selectedPortalSlug, + locale: this.locale, + }, + }); + } catch (error) { + this.alertMessage = + error?.message || + this.$t('HELP_CENTER.DELETE_ARTICLE.API.ERROR_MESSAGE'); + } finally { + this.showAlert(this.alertMessage); + } + }, + async archiveArticle() { + try { + await this.$store.dispatch('articles/update', { + portalSlug: this.selectedPortalSlug, + articleId: this.articleId, + status: 2, + }); + this.alertMessage = this.$t('HELP_CENTER.ARCHIVE_ARTICLE.API.SUCCESS'); + } catch (error) { + this.alertMessage = + error?.message || this.$t('HELP_CENTER.ARCHIVE_ARTICLE.API.ERROR'); + } finally { + this.showAlert(this.alertMessage); + } + }, openArticleSettings() { this.showArticleSettings = 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 fcb25546b..73ccda403 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ListAllArticles.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/ListAllArticles.vue @@ -46,7 +46,6 @@ export default { ...mapGetters({ articles: 'articles/allArticles', categories: 'categories/allCategories', - selectedPortal: 'portals/getSelectedPortal', uiFlags: 'articles/uiFlags', meta: 'articles/getMeta', isFetching: 'articles/isFetching', @@ -64,7 +63,7 @@ export default { return this.isFetching && !this.articles.length; }, selectedPortalSlug() { - return this.selectedPortal?.slug; + return this.$route.params.portalSlug; }, selectedCategorySlug() { const { categorySlug } = this.$route.params; diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/NewArticle.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/NewArticle.vue index 778dc38cc..bc8ad4fab 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/NewArticle.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/articles/NewArticle.vue @@ -46,7 +46,6 @@ export default { }, computed: { ...mapGetters({ - selectedPortal: 'portals/getSelectedPortal', currentUserID: 'getCurrentUserID', articles: 'articles/articles', categories: 'categories/allCategories', @@ -58,7 +57,7 @@ export default { return { title: this.articleTitle, content: this.articleContent }; }, selectedPortalSlug() { - return this.portalSlug || this.selectedPortal?.slug; + return this.$route.params.portalSlug; }, categoryId() { return this.categories.length ? this.categories[0].id : null; diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/ListAllPortals.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/ListAllPortals.vue index 88501c9bc..9d7d09ae7 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/ListAllPortals.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/pages/portals/ListAllPortals.vue @@ -33,6 +33,7 @@