feat: add show author option to portal settings and update related views (#225)

* feat: add show author option to portal settings and update related views

* fix: update portal reference to use local variable for show author condition

* feat: enhance show_author handling in portal config and add related tests
This commit is contained in:
Gabriel Jablonski 2026-02-26 14:32:52 -03:00 committed by GitHub
parent c026ee2fc8
commit 21007bd20b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 92 additions and 4 deletions

View File

@ -23,7 +23,7 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def update
ActiveRecord::Base.transaction do
@portal.update!(portal_params.merge(live_chat_widget_params)) if params[:portal].present?
@portal.update!(merged_portal_params.merge(live_chat_widget_params)) if params[:portal].present?
# @portal.custom_domain = parsed_custom_domain
process_attached_logo if params[:blob_id].present?
rescue ActiveRecord::RecordInvalid => e
@ -79,10 +79,20 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def portal_params
params.require(:portal).permit(
:id, :color, :custom_domain, :header_text, :homepage_link,
:name, :page_title, :slug, :archived, { config: [:default_locale, { allowed_locales: [] }] }
:name, :page_title, :slug, :archived, { config: [:default_locale, :show_author, { allowed_locales: [] }] }
)
end
def merged_portal_params
update_params = portal_params.to_h
if update_params.key?('config')
base_config = @portal.config.is_a?(Hash) ? @portal.config : {}
incoming_config = update_params['config']
update_params['config'] = incoming_config.is_a?(Hash) ? base_config.merge(incoming_config) : base_config
end
update_params
end
def live_chat_widget_params
permitted_params = params.permit(:inbox_id)
return {} unless permitted_params.key?(:inbox_id)

View File

@ -15,6 +15,7 @@ import Input from 'dashboard/components-next/input/Input.vue';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
import ColorPicker from 'dashboard/components-next/colorpicker/ColorPicker.vue';
import Switch from 'dashboard/components-next/switch/Switch.vue';
const props = defineProps({
activePortal: {
@ -45,6 +46,7 @@ const state = reactive({
liveChatWidgetInboxId: '',
logoUrl: '',
avatarBlobId: '',
showAuthor: true,
});
const originalState = reactive({ ...state });
@ -117,6 +119,7 @@ watch(
homePageLink: newVal.homepage_link,
slug: newVal.slug,
liveChatWidgetInboxId: newVal.inbox?.id || '',
showAuthor: newVal.config?.show_author !== false,
});
if (newVal.logo) {
const {
@ -149,6 +152,7 @@ const handleUpdatePortal = () => {
homepage_link: state.homePageLink,
blob_id: state.avatarBlobId,
inbox_id: state.liveChatWidgetInboxId,
config: { show_author: state.showAuthor },
};
emit('updatePortal', portal);
};
@ -335,6 +339,21 @@ const handleAvatarDelete = () => {
<ColorPicker v-model="state.widgetColor" />
</div>
</div>
<div
class="grid items-start justify-between w-full gap-2 grid-cols-[200px,1fr]"
>
<label
class="text-sm font-medium whitespace-nowrap py-2.5 text-n-slate-12"
>
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.SHOW_AUTHOR.LABEL') }}
</label>
<div class="flex flex-col gap-1 py-2.5">
<Switch v-model="state.showAuthor" />
<span class="text-xs text-n-slate-11">
{{ t('HELP_CENTER.PORTAL_SETTINGS.FORM.SHOW_AUTHOR.HELP_TEXT') }}
</span>
</div>
</div>
<div class="flex justify-end w-full gap-2">
<Button
:label="t('HELP_CENTER.PORTAL_SETTINGS.FORM.SAVE_CHANGES')"

View File

@ -747,6 +747,10 @@
"BRAND_COLOR": {
"LABEL": "Brand color"
},
"SHOW_AUTHOR": {
"LABEL": "Show article authors",
"HELP_TEXT": "Display author information on category pages"
},
"SAVE_CHANGES": "Save changes"
},
"CONFIGURATION_FORM": {

View File

@ -747,6 +747,10 @@
"BRAND_COLOR": {
"LABEL": "Cor da Marca"
},
"SHOW_AUTHOR": {
"LABEL": "Mostrar autores dos artigos",
"HELP_TEXT": "Exibir informações de autoria nas páginas de categorias"
},
"SAVE_CHANGES": "Salvar Alterações"
},
"CONFIGURATION_FORM": {

View File

@ -44,7 +44,11 @@ class Portal < ApplicationRecord
scope :active, -> { where(archived: false) }
CONFIG_JSON_KEYS = %w[allowed_locales default_locale website_token].freeze
CONFIG_JSON_KEYS = %w[allowed_locales default_locale website_token show_author].freeze
def show_author?
!ActiveModel::Type::Boolean.new.cast(config['show_author']).equal?(false)
end
def file_base_data
{

View File

@ -15,6 +15,7 @@ json.config do
json.partial! 'api/v1/models/portal_config', formats: [:json], locale: locale, portal: portal
end
end
json.show_author portal.show_author?
end
if portal.channel_web_widget

View File

@ -46,8 +46,10 @@
</div>
<div class="flex justify-between flex-row items-center <%= !@is_plain_layout_enabled && 'px-2' %>">
<div class="flex flex-row items-center gap-1">
<% if portal.show_author? %>
<%= render "public/api/v1/portals/authors", category: category, show_expanded: false %>
<span class="text-slate-600 dark:text-slate-400">•</span>
<% end %>
<span class="text-sm font-medium text-slate-600 dark:text-slate-400"><%= render 'public/api/v1/portals/article_count', article_count: category.articles.published.order(position: :asc).size %></span>
</div>
<div>

View File

@ -22,8 +22,10 @@
<% end %>
</div>
<div class="flex flex-row items-center gap-1">
<% if portal.show_author? %>
<%= render "public/api/v1/portals/authors", category: category, show_expanded: true %>
<span class="text-slate-600 dark:text-slate-400">•</span>
<% end %>
<span class="flex items-center text-base text-slate-600 dark:text-slate-400 font-medium"><%= render 'public/api/v1/portals/article_count', article_count: category.articles.published.size %></span>
</div>
</div>

View File

@ -131,7 +131,49 @@ RSpec.describe 'Api::V1::Accounts::Portals', type: :request do
json_response = response.parsed_body
expect(json_response['name']).to eql(portal_params[:portal][:name])
expect(json_response['config']).to eql({ 'allowed_locales' => [{ 'articles_count' => 0, 'categories_count' => 0, 'code' => 'en' },
{ 'articles_count' => 0, 'categories_count' => 0, 'code' => 'es' }] })
{ 'articles_count' => 0, 'categories_count' => 0, 'code' => 'es' }],
'show_author' => true })
end
it 'persists show_author as false when explicitly set' do
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
params: { portal: { config: { show_author: false } } },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
json_response = response.parsed_body
expect(json_response['config']['show_author']).to be(false)
portal.reload
expect(portal.show_author?).to be(false)
end
it 'preserves show_author when updating other portal fields' do
portal.update!(config: portal.config.merge('show_author' => false))
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
params: { portal: { name: 'renamed_portal' } },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
portal.reload
expect(portal.show_author?).to be(false)
expect(portal.name).to eql('renamed_portal')
end
it 'preserves show_author when updating only allowed_locales' do
portal.update!(config: portal.config.merge('show_author' => false))
put "/api/v1/accounts/#{account.id}/portals/#{portal.slug}",
params: { portal: { config: { allowed_locales: %w[en fr] } } },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
portal.reload
expect(portal.show_author?).to be(false)
end
it 'archive portal' do