Skip to content

Commit

Permalink
feat: Separate page for Publications
Browse files Browse the repository at this point in the history
martintomas committed Oct 19, 2023
1 parent 462f996 commit c7ab75a
Showing 12 changed files with 135 additions and 49 deletions.
6 changes: 6 additions & 0 deletions app/assets/stylesheets/tpi/_pages.scss
Original file line number Diff line number Diff line change
@@ -174,6 +174,12 @@ $margin-between-list-items: 20px;
flex-direction: column;
}
}

&__flex {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
}

.pages__content-title {
22 changes: 13 additions & 9 deletions app/controllers/tpi/publications_controller.rb
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ class PublicationsController < TPIController
before_action :fetch_tags, only: [:index]
before_action :fetch_sectors, only: [:index]
before_action :fetch_publication, only: [:show]
before_action :fetch_news_article, only: [:show_news_article]

def index
results = Queries::TPI::NewsPublicationsQuery.new(filter_params).call
@@ -36,14 +37,17 @@ def show
respond_to do |format|
format.html do
admin_panel_path = polymorphic_path([:admin, @publication])
fixed_navbar("#{@publication.class.name.underscore.humanize} #{@publication.title}", admin_panel_path)

redirect_to '' unless @publication
fixed_navbar(@publication.title.to_s, admin_panel_path)
end
format.pdf { stream_publication_file }
end
end

def show_news_article
admin_panel_path = polymorphic_path([:admin, @news_article])
fixed_navbar((@news_article[:title]).to_s, admin_panel_path)
end

def download_file
@publication = Publication.published.find_by! slug: params[:slug]
stream_publication_file
@@ -70,15 +74,15 @@ def offset
end

def filter_params
params.permit(:tags, :sectors)
params.permit(:tags, :sectors, :types)
end

def fetch_publication
@publication = if params[:type].eql?('NewsArticle')
NewsArticle.published.find(params[:id])
else
Publication.published.find_by(id: params[:id]) || Publication.published.find_by!(slug: params[:id])
end
@publication = Publication.published.find_by(id: params[:id]) || Publication.published.find_by!(slug: params[:id])
end

def fetch_news_article
@news_article = NewsArticle.published.find params[:id]
end

def fetch_tags
65 changes: 55 additions & 10 deletions app/javascript/components/tpi/Filters.js
Original file line number Diff line number Diff line change
@@ -9,20 +9,25 @@ import { useQueryParam } from 'shared/hooks';
const ALL_OPTION_NAME = 'All';
const SHOW_ON_PAGE = 9;

const Filters = ({ tags, sectors, resultsSize }) => {
const Filters = ({ types, tags, sectors, resultsSize }) => {
const [isFilterOpen, setIsFiltersOpen] = useState(false);
const [resultsCount, setResultsCount] = useState(resultsSize);
const [queryTypesParam, setQueryTypes] = useQueryParam('types');
const [queryTagsParam, setQueryTags] = useQueryParam('tags');
const [querySectorsParam, setQuerySectors] = useQueryParam('sectors');
const [offset, setOffset] = useState(0);

const activeTypes = useMemo(() => {
const typesWithAllOption = [ALL_OPTION_NAME, ...types];
const queryTypes = (queryTypesParam || '').split(',').filter(x => x);
return typesWithAllOption.map(type => ({
name: type,
active: queryTypes.length > 0 ? queryTypes.includes(type) : type === ALL_OPTION_NAME
}));
}, [types, queryTypesParam]);
const activeTags = useMemo(() => {
const queryTags = (queryTagsParam || '').split(',').filter(x => x);
const tagsWithAllOption = [
ALL_OPTION_NAME,
...queryTags.filter(x => !tags.includes(x)),
...tags
];
const tagsWithAllOption = [ALL_OPTION_NAME, ...tags];
return tagsWithAllOption.map(tag => ({
name: tag,
active: queryTags.length > 0 ? queryTags.includes(tag) : tag === ALL_OPTION_NAME
@@ -71,7 +76,11 @@ const Filters = ({ tags, sectors, resultsSize }) => {
};
}, [handleLoadMore]);

const refreshPublicationsHtml = (_tags, _sectors, _offset) => {
const refreshPublicationsHtml = (_types, _tags, _sectors, _offset) => {
const activeTypesQueryParam = _types
.filter(t => t.active && t.name !== ALL_OPTION_NAME)
.map(t => encodeURIComponent(t.name))
.join(', ');
const activeTagsQueryParam = _tags
.filter(t => t.active && t.name !== ALL_OPTION_NAME)
.map(t => encodeURIComponent(t.name))
@@ -81,7 +90,7 @@ const Filters = ({ tags, sectors, resultsSize }) => {
.map(s => encodeURIComponent(s.name))
.join(', ');

const query = `tags=${activeTagsQueryParam}&sectors=${activeSectorsQueryParam}&offset=${_offset}`;
const query = `types=${activeTypesQueryParam}&tags=${activeTagsQueryParam}&sectors=${activeSectorsQueryParam}&offset=${_offset}`;
const url = `/publications/partial?${query}`;

fetch(url)
@@ -98,8 +107,31 @@ const Filters = ({ tags, sectors, resultsSize }) => {
};

useEffect(() => {
refreshPublicationsHtml(activeTags, activeSectors, offset);
}, [activeTags, activeSectors, offset]);
refreshPublicationsHtml(activeTypes, activeTags, activeSectors, offset);
}, [activeTypes, activeTags, activeSectors, offset]);

const handleTypeClick = (type) => {
const otherOptions = optionsWithoutALL(activeTypes);

const shouldALLbeSelected = isAllClicked(type) && (isOtherOptionsActive(otherOptions) || isAllSelected(otherOptions));

if (shouldALLbeSelected) {
const typesWithALLSelected = activeTypes.map(s => ({
name: s.name,
active: s.name === ALL_OPTION_NAME
}));
setOffset(0);
setQueryTypes(typesWithALLSelected.filter(s => s.active).map((s) => s.name).join(','));
} else {
const updatedTypes = activeTypes.map(s => {
if (s.name === type.name) { return { name: s.name, active: !s.active }; }
if (s.name === ALL_OPTION_NAME) { return { name: s.name, active: false }; }
return { name: s.name, active: s.active };
});
setOffset(0);
setQueryTypes(updatedTypes.filter(s => s.active).map((s) => s.name).join(','));
}
};

const handleTagClick = (tag) => {
const otherOptions = optionsWithoutALL(activeTags);
@@ -172,6 +204,18 @@ const Filters = ({ tags, sectors, resultsSize }) => {
{isFilterOpen
&& (
<div className="filters__container">
<div className="filters__title">Type</div>
<div className="filters__tags-container">
{activeTypes.length && activeTypes.map(type => (
<button
type="button"
onClick={() => handleTypeClick(type)}
className={cx('filters__tag', {'filters__tag-selected': type.active})}
>
{type.name}
</button>
))}
</div>
<div className="filters__title">Tag</div>
<div className="filters__tags-container">
{activeTags.length && activeTags.map(tag => (
@@ -204,6 +248,7 @@ const Filters = ({ tags, sectors, resultsSize }) => {
};

Filters.propTypes = {
types: PropTypes.array.isRequired,
tags: PropTypes.array.isRequired,
sectors: PropTypes.array.isRequired,
resultsSize: PropTypes.number.isRequired
6 changes: 5 additions & 1 deletion app/services/queries/tpi/news_publications_query.rb
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ class NewsPublicationsQuery
include ActiveModel::Model

attr_reader :publications_scope, :news_scope
attr_accessor :tags, :sectors
attr_accessor :tags, :sectors, :types

def call
(publications + news).uniq.sort_by(&:publication_date).reverse!
@@ -13,10 +13,14 @@ def call
private

def publications
return Publication.none if types.present? && !types.include?('Publications')

filter_scope(Publication.published)
end

def news
return NewsArticle.none if types.present? && !types.include?('News')

filter_scope(NewsArticle.published)
end

4 changes: 2 additions & 2 deletions app/views/tpi/publications/_list.html.erb
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@
<div class="content__title-wrapper">
<div class="content__title">
<% if publication.is_a?(Publication) %>
<%= link_to publication.title, tpi_publication_download_file_path(slug: publication.slug), target: '_blank', class: 'link is-strong' %>
<%= link_to publication.title, tpi_publication_path(id: publication.slug), class: 'link is-strong' %>
<% else %>
<%= link_to publication.title, tpi_publication_path(id: publication.id, type: publication.class.name), class: "link is-strong" %>
<%= link_to publication.title, show_news_article_tpi_publication_path(id: publication.id), class: "link is-strong" %>
<% end %>
</div>
</div>
7 changes: 6 additions & 1 deletion app/views/tpi/publications/index.html.erb
Original file line number Diff line number Diff line change
@@ -7,7 +7,12 @@
<img src="<%= asset_path 'icons/filter-blue.svg'%>" class="filters__filter-icon" alt="filter icon">
</button>
</div>
<%= react_component("Filters", { tags: @tags, sectors: @sectors, resultsSize: @publications_and_articles_count }) %>
<%= react_component("Filters", {
types: %w[Publications News],
tags: @tags,
sectors: @sectors,
resultsSize: @publications_and_articles_count
}) %>
<div id="publications" class="publications__container container">
<%= render 'promoted', publications_and_articles: @publications_and_articles, count: @publications_and_articles_count %>
</div>
30 changes: 17 additions & 13 deletions app/views/tpi/publications/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
<% content_for :page_title, "#{@publication.title} - Transition Pathway Initiative" %>
<% if @publication.class.name.eql?("NewsArticle") %>
<% content_for :page_description, strip_tags(@publication.content).first(160) if @publication.content.present? %>
<% else %>
<% content_for :page_description, @publication.short_description&.first(160) %>
<% end %>
<% content_for :page_description, @publication.short_description&.first(160) %>

<div class="container">
<div class="pages__grid">
<div class="pages__content">
<h1 class="pages__title"><%= @publication.title %></h1>
<p class="pages__date"><%= @publication.publication_date.strftime('%d/%m/%Y') %></p>
<% if @publication.class.name.eql?("NewsArticle") && @publication.image.present? %>
<div class="pages__image"><%= image_tag(@publication.image) %></div>
<% end %>
<div class="pages__description">
<% if @publication.class.name.eql?("NewsArticle") %>
<%= @publication.content&.html_safe %>
<% else %>
<%= @publication.short_description %>

<div class="pages__content__flex">
<% if @publication.image.present? %>
<div class="pages__image"><%= image_tag(@publication.image) %></div>
<% end %>
<div class="pages__description">
<p><%= @publication.short_description %></p>
<% if @publication.keywords.any? || @publication.tpi_sectors.any? %>
<div class="publication__tags">
<% @publication.tags_and_sectors.each do |tag| %>
<span class="tag"><%= tag %></span>
<% end %>
</div>
<% end %>

<%= link_to 'Download file', tpi_publication_download_file_path(slug: @publication.slug), target: '_blank', class: 'button is-primary' %>
</div>
</div>
</div>
</div>
17 changes: 17 additions & 0 deletions app/views/tpi/publications/show_news_article.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<% content_for :page_title, "#{@news_article.title} - Transition Pathway Initiative" %>
<% content_for :page_description, strip_tags(@news_article.content).first(160) if @news_article.content.present? %>

<div class="container">
<div class="pages__grid">
<div class="pages__content">
<h1 class="pages__title"><%= @news_article.title %></h1>
<p class="pages__date"><%= @news_article.publication_date.strftime('%d/%m/%Y') %></p>
<% if @news_article.image.present? %>
<div class="pages__image"><%= image_tag(@news_article.image) %></div>
<% end %>
<div class="pages__description">
<%= @news_article.content&.html_safe %>
</div>
</div>
</div>
</div>
4 changes: 2 additions & 2 deletions app/views/tpi/sitemaps/index.xml.erb
Original file line number Diff line number Diff line change
@@ -40,12 +40,12 @@
</url>
<% @publications.each do |publication| %>
<url>
<loc><%= "#{@host}#{tpi_publication_download_file_path(slug: publication.slug)}" %></loc>
<loc><%= "#{@host}#{tpi_publication_path(id: publication.slug)}" %></loc>
</url>
<% end %>
<% @news.each do |news| %>
<url>
<loc><%= "#{@host}#{tpi_publication_path(news, type: 'NewsArticle')}" %></loc>
<loc><%= "#{@host}#{show_news_article_tpi_publication_path(id: news.id)}" %></loc>
</url>
<% end %>
</urlset>
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -55,6 +55,9 @@
end

resources :publications, only: [:index, :show] do
member do
get :show_news_article
end
collection do
get :partial
end
10 changes: 5 additions & 5 deletions spec/controllers/tpi/publications_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -29,19 +29,19 @@
describe 'GET show' do
context 'publication' do
context 'published' do
subject { get :show, params: {id: publication1.id, type: 'Publication'} }
subject { get :show, params: {id: publication1.id} }

it { is_expected.to be_successful }

context 'when publication is searched by slug' do
subject { get :show, params: {id: publication1.slug, type: 'Publication'} }
subject { get :show, params: {id: publication1.slug} }

it { is_expected.to be_successful }
end
end

context 'unpublished' do
subject { get :show, params: {id: publication4.id, type: 'Publication'} }
subject { get :show, params: {id: publication4.slug} }

it 'not found' do
expect { subject }.to raise_exception(ActiveRecord::RecordNotFound)
@@ -51,13 +51,13 @@

context 'news article' do
context 'published' do
subject { get :show, params: {id: news_article1.id, type: 'NewsArticle'} }
subject { get :show_news_article, params: {id: news_article1.id} }

it { is_expected.to be_successful }
end

context 'unpublished' do
subject { get :show, params: {id: news_article4.id, type: 'NewsArticle'} }
subject { get :show_news_article, params: {id: news_article4.id} }

it 'not found' do
expect { subject }.to raise_exception(ActiveRecord::RecordNotFound)
10 changes: 4 additions & 6 deletions spec/controllers/tpi/sitemaps_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require 'rails_helper'

# rubocop:disable Layout/LineLength
RSpec.describe TPI::SitemapsController, type: :controller do
let_it_be(:company1) { create(:company, :published) }
let_it_be(:company2) { create(:company, :draft) }
@@ -27,16 +26,15 @@
expect(response.body).to have_css('url loc', text: tpi_company_url(company1.slug, **host_params))
expect(response.body).to have_css('url loc', text: tpi_bank_url(bank.slug, **host_params))
expect(response.body).to have_css('url loc', text: "https://#{host_params[:host]}/#{page1.slug}")
expect(response.body).to have_css('url loc', text: tpi_publication_download_file_path(slug: publication1.slug, **host_params))
expect(response.body).to have_css('url loc', text: tpi_publication_url(article1, type: 'NewsArticle', **host_params))
expect(response.body).to have_css('url loc', text: tpi_publication_url(id: publication1.slug, **host_params))
expect(response.body).to have_css('url loc', text: show_news_article_tpi_publication_path(article1, **host_params))
end

it('should not return unpublished entities') do
subject
expect(response.body).not_to have_css('url loc', text: tpi_company_url(company2.slug, **host_params))
expect(response.body).not_to have_css('url loc', text: tpi_publication_download_file_path(slug: publication2.slug, **host_params))
expect(response.body).not_to have_css('url loc', text: tpi_publication_url(article2, type: 'NewsArticle', **host_params))
expect(response.body).not_to have_css('url loc', text: tpi_publication_url(id: publication2.slug, **host_params))
expect(response.body).not_to have_css('url loc', text: show_news_article_tpi_publication_path(article2, **host_params))
end
end
end
# rubocop:enable Layout/LineLength

0 comments on commit c7ab75a

Please sign in to comment.