Merge pull request #9655 from colinux/refactor-main-navigation
Administrateurs/Instructeurs/Experts : lien vers la page des nouveautés et refactorise les barres de navigation principale
This commit is contained in:
commit
9a5e4d4ea5
35 changed files with 457 additions and 96 deletions
1
Gemfile
1
Gemfile
|
@ -4,7 +4,6 @@ gem 'rails', '~> 7.0.5' # allows update to security fixes at any time
|
||||||
|
|
||||||
gem 'aasm'
|
gem 'aasm'
|
||||||
gem 'acsv'
|
gem 'acsv'
|
||||||
gem 'active_link_to' # Automatically set a class on active links
|
|
||||||
gem 'active_model_serializers'
|
gem 'active_model_serializers'
|
||||||
gem 'activestorage-openstack'
|
gem 'activestorage-openstack'
|
||||||
gem 'active_storage_validations'
|
gem 'active_storage_validations'
|
||||||
|
|
|
@ -49,9 +49,6 @@ GEM
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
active_link_to (1.0.5)
|
|
||||||
actionpack
|
|
||||||
addressable
|
|
||||||
active_model_serializers (0.10.13)
|
active_model_serializers (0.10.13)
|
||||||
actionpack (>= 4.1, < 7.1)
|
actionpack (>= 4.1, < 7.1)
|
||||||
activemodel (>= 4.1, < 7.1)
|
activemodel (>= 4.1, < 7.1)
|
||||||
|
@ -814,7 +811,6 @@ PLATFORMS
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
aasm
|
aasm
|
||||||
acsv
|
acsv
|
||||||
active_link_to
|
|
||||||
active_model_serializers
|
active_model_serializers
|
||||||
active_storage_validations
|
active_storage_validations
|
||||||
activestorage-openstack
|
activestorage-openstack
|
||||||
|
|
|
@ -13,3 +13,14 @@ span.notifications {
|
||||||
top: 5px;
|
top: 5px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fr-nav {
|
||||||
|
&__notifiable {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications {
|
||||||
|
top: 1rem;
|
||||||
|
right: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
24
app/components/main_navigation/announces_link_component.rb
Normal file
24
app/components/main_navigation/announces_link_component.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MainNavigation::AnnouncesLinkComponent < ApplicationComponent
|
||||||
|
def render?
|
||||||
|
# also see app/controllers/release_notes_controller.rb#ensure_access_allowed!
|
||||||
|
return false if !helpers.instructeur_signed_in? && !helpers.administrateur_signed_in? && !helpers.expert_signed_in?
|
||||||
|
|
||||||
|
@most_recent_released_on = load_most_recent_released_on
|
||||||
|
|
||||||
|
@most_recent_released_on.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def something_new?
|
||||||
|
return true if current_user.announces_seen_at.nil?
|
||||||
|
|
||||||
|
@most_recent_released_on.after? current_user.announces_seen_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_most_recent_released_on
|
||||||
|
categories = helpers.infer_default_announce_categories
|
||||||
|
|
||||||
|
ReleaseNote.most_recent_announce_date_for_categories(categories)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
en:
|
||||||
|
news: News
|
||||||
|
something_new: New informations about the website may be of interest to you.
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
news: Nouveautés
|
||||||
|
something_new: De nouvelles informations à propos du site pourraient vous intéresser.
|
|
@ -0,0 +1,4 @@
|
||||||
|
%li.fr-nav__item.fr-nav__notifiable
|
||||||
|
= link_to t('.news'), release_notes_path, class: "fr-nav__link",'aria-current': current_page?(release_notes_path) ? 'page' : nil
|
||||||
|
- if something_new?
|
||||||
|
%span.notifications{ 'aria-label': t('.something_new') }
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MainNavigation::InstructeurExpertNavigationComponent < ApplicationComponent
|
||||||
|
def instructeur?
|
||||||
|
helpers.instructeur_signed_in?
|
||||||
|
end
|
||||||
|
|
||||||
|
def expert?
|
||||||
|
helpers.expert_signed_in?
|
||||||
|
end
|
||||||
|
|
||||||
|
def aria_current_for(page)
|
||||||
|
{ current: page == current_page ? :page : nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def current_page
|
||||||
|
case controller_name
|
||||||
|
when 'avis'
|
||||||
|
:avis
|
||||||
|
when 'procedures', 'dossiers'
|
||||||
|
:procedure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
%nav#header-navigation.fr-nav{ role: :navigation, "aria-label" => t('main_menu', scope: [:layouts, :header]) }
|
||||||
|
%ul.fr-nav__list
|
||||||
|
- if instructeur?
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to Procedure.model_name.human(count: 10), instructeur_procedures_path, class: 'fr-nav__link', aria: aria_current_for(:procedure)
|
||||||
|
|
||||||
|
- if expert?
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to expert_all_avis_path, class: 'fr-nav__link', aria: aria_current_for(:avis) do
|
||||||
|
= Avis.model_name.human(count: 10)
|
||||||
|
- if helpers.current_expert.avis_summary[:unanswered] > 0
|
||||||
|
%span.badge.warning= helpers.current_expert.avis_summary[:unanswered]
|
||||||
|
|
||||||
|
= render MainNavigation::AnnouncesLinkComponent.new
|
|
@ -1,8 +1,9 @@
|
||||||
class ReleaseNotesController < ApplicationController
|
class ReleaseNotesController < ApplicationController
|
||||||
before_action :ensure_access_allowed!
|
before_action :ensure_access_allowed!
|
||||||
|
after_action :touch_default_categories_seen_at
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@categories = params[:categories].presence || infer_default_categories
|
@categories = params[:categories].presence || helpers.infer_default_announce_categories
|
||||||
|
|
||||||
# Paginate per group of dates, then show all announces for theses dates
|
# Paginate per group of dates, then show all announces for theses dates
|
||||||
@paginated_groups = ReleaseNote.published
|
@paginated_groups = ReleaseNote.published
|
||||||
|
@ -20,18 +21,26 @@ class ReleaseNotesController < ApplicationController
|
||||||
render "scrollable_list" if params[:page].present?
|
render "scrollable_list" if params[:page].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def nav_bar_profile
|
||||||
|
# detect context from referer, simple (no detection when refreshing the page)
|
||||||
|
params = Rails.application.routes.recognize_path(request&.referer)
|
||||||
|
|
||||||
|
controller_class = "#{params[:controller].camelize}Controller".safe_constantize
|
||||||
|
return if controller_class.nil?
|
||||||
|
|
||||||
|
controller_instance = controller_class.new
|
||||||
|
controller_instance.try(:nav_bar_profile)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def infer_default_categories
|
def touch_default_categories_seen_at
|
||||||
if administrateur_signed_in?
|
return if params[:categories].present? || params[:page].present?
|
||||||
['administrateur', 'usager', current_administrateur.api_tokens.exists? ? 'api' : nil]
|
return if current_user.blank?
|
||||||
elsif instructeur_signed_in?
|
|
||||||
['instructeur', 'expert']
|
return if current_user.announces_seen_at&.after?(@announces.max_by(&:released_on).released_on)
|
||||||
elsif expert_signed_in?
|
|
||||||
['expert']
|
current_user.touch(:announces_seen_at)
|
||||||
else
|
|
||||||
['usager']
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_access_allowed!
|
def ensure_access_allowed!
|
||||||
|
|
|
@ -76,6 +76,10 @@ module Users
|
||||||
retrieve_procedure
|
retrieve_procedure
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def nav_bar_profile
|
||||||
|
current_user ? :user : :guest
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def extra_query_params
|
def extra_query_params
|
||||||
|
|
|
@ -15,4 +15,30 @@ module ReleaseNotesHelper
|
||||||
|
|
||||||
content_tag(:span, ReleaseNote.human_attribute_name("categories.#{category}"), class: "fr-badge #{color_class}")
|
content_tag(:span, ReleaseNote.human_attribute_name("categories.#{category}"), class: "fr-badge #{color_class}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def infer_default_announce_categories
|
||||||
|
if administrateur_signed_in?
|
||||||
|
ReleaseNote.default_categories_for_role(:administrateur, current_administrateur)
|
||||||
|
elsif instructeur_signed_in?
|
||||||
|
ReleaseNote.default_categories_for_role(:instructeur, current_instructeur)
|
||||||
|
elsif expert_signed_in?
|
||||||
|
ReleaseNote.default_categories_for_role(:expert, current_expert)
|
||||||
|
else
|
||||||
|
ReleaseNote.default_categories_for_role(:usager)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_release_note_content(content)
|
||||||
|
allowed_attributes.merge ["rel", "target"] # title already allowed
|
||||||
|
|
||||||
|
content.body.fragment.source.css("a[href]").each do |link|
|
||||||
|
uri = URI.parse(link['href'])
|
||||||
|
|
||||||
|
link.set_attribute('rel', 'noreferrer noopener')
|
||||||
|
link.set_attribute('target', '_blank')
|
||||||
|
link.set_attribute('title', new_tab_suffix(uri.host))
|
||||||
|
end
|
||||||
|
|
||||||
|
content
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,4 +14,21 @@ class ReleaseNote < ApplicationRecord
|
||||||
|
|
||||||
scope :published, -> { where(published: true, released_on: ..Date.current) }
|
scope :published, -> { where(published: true, released_on: ..Date.current) }
|
||||||
scope :for_categories, -> (categories) { where("categories && ARRAY[?]::varchar[]", categories) }
|
scope :for_categories, -> (categories) { where("categories && ARRAY[?]::varchar[]", categories) }
|
||||||
|
|
||||||
|
def self.default_categories_for_role(role, instance = nil)
|
||||||
|
case role
|
||||||
|
when :administrateur
|
||||||
|
['administrateur', 'usager', instance.api_tokens.exists? ? 'api' : nil]
|
||||||
|
when :instructeur
|
||||||
|
['instructeur', instance.user.expert? ? 'expert' : nil]
|
||||||
|
when :expert
|
||||||
|
['expert', instance.user.instructeur? ? 'instructeur' : nil]
|
||||||
|
else
|
||||||
|
['usager']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.most_recent_announce_date_for_categories(categories)
|
||||||
|
published.for_categories(categories).maximum(:released_on)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
||||||
<% if blob.representable? %>
|
<% if blob.representable? %>
|
||||||
<%= image_tag blob.representation(resize_to_fit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<figcaption class="attachment__caption">
|
<figcaption class="attachment__caption">
|
||||||
|
|
7
app/views/administrateurs/_main_navigation.html.haml
Normal file
7
app/views/administrateurs/_main_navigation.html.haml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
%nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal administrateur' }
|
||||||
|
%ul.fr-nav__list
|
||||||
|
%li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(controller: 'administrateurs/procedures', action: :index) ? 'page' : nil
|
||||||
|
- if Rails.application.config.ds_zonage_enabled
|
||||||
|
%li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil
|
||||||
|
|
||||||
|
= render MainNavigation::AnnouncesLinkComponent.new
|
|
@ -1,6 +0,0 @@
|
||||||
.fr-container
|
|
||||||
%nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal administrateur' }
|
|
||||||
%ul.fr-nav__list
|
|
||||||
%li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(controller: 'procedures', action: :index) ? 'true' : nil
|
|
||||||
- if Rails.application.config.ds_zonage_enabled
|
|
||||||
%li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil
|
|
|
@ -1,5 +1,3 @@
|
||||||
= render 'main_menu'
|
|
||||||
|
|
||||||
.sub-header
|
.sub-header
|
||||||
.procedure-admin-listing-container
|
.procedure-admin-listing-container
|
||||||
= link_to "Nouvelle Démarche", new_from_existing_admin_procedures_path, id: 'new-procedure', class: 'fr-btn'
|
= link_to "Nouvelle Démarche", new_from_existing_admin_procedures_path, id: 'new-procedure', class: 'fr-btn'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.fr-container
|
- content_for(:main_navigation) do
|
||||||
%nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal gestionnaire' }
|
%nav#header-navigation.fr-nav{ role: 'navigation', 'aria-label': 'Menu principal gestionnaire' }
|
||||||
%ul.fr-nav__list
|
%ul.fr-nav__list
|
||||||
%li.fr-nav__item= link_to 'Mes groupes gestionnaire', gestionnaire_groupe_gestionnaires_path, class:'fr-nav__link', 'aria-current': current_page?(controller: 'groupe_gestionnaires', action: :index) ? 'true' : nil
|
%li.fr-nav__item= link_to 'Mes groupes gestionnaire', gestionnaire_groupe_gestionnaires_path, class:'fr-nav__link', 'aria-current': current_page?(controller: 'groupe_gestionnaires', action: :index) ? 'page' : nil
|
|
@ -1,4 +1,4 @@
|
||||||
= render 'main_menu'
|
= render 'main_navigation'
|
||||||
|
|
||||||
.sub-header
|
.sub-header
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
- dossier = controller.try(:dossier_for_help)
|
- dossier = controller.try(:dossier_for_help)
|
||||||
- procedure = controller.try(:procedure_for_help)
|
- procedure = controller.try(:procedure_for_help)
|
||||||
- is_instructeur_context = nav_bar_profile == :instructeur && instructeur_signed_in?
|
- is_instructeur_context = nav_bar_profile == :instructeur && instructeur_signed_in?
|
||||||
|
- is_administrateur_context = nav_bar_profile == :administrateur && administrateur_signed_in?
|
||||||
- is_expert_context = nav_bar_profile == :expert && expert_signed_in?
|
- is_expert_context = nav_bar_profile == :expert && expert_signed_in?
|
||||||
- is_user_context = nav_bar_profile == :user
|
- is_user_context = nav_bar_profile == :user
|
||||||
- is_search_enabled = [params[:controller] == 'recherche', is_instructeur_context, is_expert_context, is_user_context && current_user.dossiers.count].any?
|
- is_search_enabled = [params[:controller] == 'recherche', is_instructeur_context, is_expert_context, is_user_context && current_user.dossiers.count].any?
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
.fr-header__navbar
|
.fr-header__navbar
|
||||||
- if is_search_enabled
|
- if is_search_enabled
|
||||||
%button.fr-btn--search.fr-btn{ "aria-controls" => "search-modal", "data-fr-opened" => "false", :title => t('views.users.dossiers.search.search_file') }= t('views.users.dossiers.search.search_file')
|
%button.fr-btn--search.fr-btn{ "aria-controls" => "search-modal", "data-fr-opened" => "false", :title => t('views.users.dossiers.search.search_file') }= t('views.users.dossiers.search.search_file')
|
||||||
%button.fr-btn--menu.fr-btn{ "aria-controls" => "burger-menu", "aria-haspopup" => "menu", "data-fr-opened" => "false", :title => "Menu" } Menu
|
%button#navbar-burger-button.fr-btn--menu.fr-btn{ "aria-controls" => "modal-header__menu", "aria-haspopup" => "menu", "data-fr-opened" => "false", title: "Menu" } Menu
|
||||||
.fr-header__service
|
.fr-header__service
|
||||||
- root_profile_link, root_profile_libelle = root_path_info_for_profile(nav_bar_profile)
|
- root_profile_link, root_profile_libelle = root_path_info_for_profile(nav_bar_profile)
|
||||||
|
|
||||||
|
@ -68,39 +69,20 @@
|
||||||
- if is_expert_context
|
- if is_expert_context
|
||||||
= render partial: 'layouts/search_dossiers_form'
|
= render partial: 'layouts/search_dossiers_form'
|
||||||
|
|
||||||
- has_header = [is_instructeur_context, is_expert_context, is_user_context]
|
#modal-header__menu.fr-header__menu.fr-modal{ "aria-labelledby": "navbar-burger-button" }
|
||||||
#burger-menu.fr-header__menu.fr-modal
|
|
||||||
.fr-container
|
.fr-container
|
||||||
%button#burger_button.fr-btn--close.fr-btn{ "aria-controls" => "burger-menu", :title => t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header])
|
%button.fr-btn--close.fr-btn{ "aria-controls" => "modal-header__menu", title: t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header])
|
||||||
.fr-header__menu-links
|
.fr-header__menu-links
|
||||||
%nav#navigation-478.fr-nav{ "aria-label" => t('main_menu', scope: [:layouts, :header]) , :role => "navigation" }
|
-# populated by dsfr js
|
||||||
%ul.fr-nav__list
|
|
||||||
-# Questionner UX pour un back JS
|
|
||||||
- if params[:controller] == 'users/commencer'
|
|
||||||
%li.fr-nav__item
|
|
||||||
= link_to t('back', scope: [:layouts, :header]), url_for(:back), title: t('back_title', scope: [:layouts, :header]), class: 'fr-nav__link'
|
|
||||||
|
|
||||||
- if is_instructeur_context
|
- if content_for?(:main_navigation)
|
||||||
- if current_instructeur.procedures.any?
|
= yield(:main_navigation)
|
||||||
- current_url = request.path_info
|
- elsif is_administrateur_context
|
||||||
%li.fr-nav__item
|
= render 'administrateurs/main_navigation'
|
||||||
= active_link_to t('utils.procedure'), instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'fr-nav__link', aria: { current: current_url == instructeur_procedures_path ? 'page' : true }
|
- elsif is_instructeur_context || is_expert_context
|
||||||
- if current_instructeur.user.expert && current_expert.avis_summary[:total] > 0
|
= render MainNavigation::InstructeurExpertNavigationComponent.new
|
||||||
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
- elsif is_user_context
|
||||||
|
= render 'users/main_navigation'
|
||||||
|
|
||||||
- if is_expert_context
|
|
||||||
- if current_expert.user.instructeur && current_instructeur.procedures.any?
|
|
||||||
%li.fr-nav__item= active_link_to t('utils.procedure'), instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'fr-nav__link', aria: { current: true }
|
|
||||||
- if current_expert.avis_summary[:total] > 0
|
|
||||||
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
|
||||||
|
|
||||||
- if is_user_context
|
|
||||||
%li.fr-nav__item= active_link_to t('.files'), dossiers_path, active: :inclusive, class: 'fr-nav__link', aria: { current: true }
|
|
||||||
- if current_user.expert && current_expert.avis_summary[:total] > 0
|
|
||||||
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
|
||||||
|
|
||||||
- if content_for?(:navigation_principale)
|
|
||||||
.fr-container
|
|
||||||
= yield(:navigation_principale)
|
|
||||||
|
|
||||||
= yield(:notice_info)
|
= yield(:notice_info)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
- content_for(:main_navigation) do
|
||||||
|
= render 'administrateurs/main_navigation'
|
||||||
|
|
||||||
- content_for :content do
|
- content_for :content do
|
||||||
= render 'main_menu'
|
|
||||||
.fr-container
|
.fr-container
|
||||||
%h1.fr-my-4w Toutes les démarches
|
%h1.fr-my-4w Toutes les démarches
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
%li.fr-nav__item
|
|
||||||
= active_link_to expert_all_avis_path, active: controller_name == 'avis', class: 'fr-nav__link' do
|
|
||||||
Avis
|
|
||||||
- if current_expert.avis_summary[:unanswered] > 0
|
|
||||||
%span.badge.warning= current_expert.avis_summary[:unanswered]
|
|
|
@ -2,9 +2,9 @@
|
||||||
%h3= l(notes[0].released_on, format: :long)
|
%h3= l(notes[0].released_on, format: :long)
|
||||||
|
|
||||||
- notes.each do |note|
|
- notes.each do |note|
|
||||||
.fr-mb-4w.fr-px-2w.fr-py-2w.fr-background-alt--grey
|
.fr-mb-4w.fr-px-2w.fr-py-2w.fr-background-alt--grey{ data: { turbo: "false" } }
|
||||||
%p
|
%p
|
||||||
- note.categories.each do |category|
|
- note.categories.each do |category|
|
||||||
= announce_category_badge(category)
|
= announce_category_badge(category)
|
||||||
|
|
||||||
= note.body
|
= render_release_note_content(note.body)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
- content_for(:navigation_principale) do
|
- content_for(:main_navigation) do
|
||||||
.fr-container
|
|
||||||
%nav.fr-nav#header-navigation{ role: "navigation", aria: { label: 'Menu principal annonces' } }
|
%nav.fr-nav#header-navigation{ role: "navigation", aria: { label: 'Menu principal annonces' } }
|
||||||
%ul.fr-nav__list
|
%ul.fr-nav__list
|
||||||
%li.fr-nav__item
|
%li.fr-nav__item
|
||||||
|
|
8
app/views/users/_main_navigation.html.haml
Normal file
8
app/views/users/_main_navigation.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
%nav#header-navigation.fr-nav{ role: :navigation, "aria-label" => t('main_menu', scope: [:layouts, :header]) }
|
||||||
|
%ul.fr-nav__list
|
||||||
|
- if params[:controller] == 'users/commencer'
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to t('back', scope: [:layouts, :header]), url_for(:back), title: t('back_title', scope: [:layouts, :header]), class: 'fr-nav__link', "aria-controls" => "modal-header__menu"
|
||||||
|
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to t('files', scope: [:layouts, :header]), dossiers_path, class: 'fr-nav__link', aria: { current: current_page?(dossiers_path) ? 'page' : nil, controls: "modal-header__menu" }
|
|
@ -56,7 +56,6 @@ en:
|
||||||
subject: Subject
|
subject: Subject
|
||||||
message: Message
|
message: Message
|
||||||
send_mail: Send message
|
send_mail: Send message
|
||||||
procedure: Procedures
|
|
||||||
new_tab: New tab
|
new_tab: New tab
|
||||||
helpers:
|
helpers:
|
||||||
procedure:
|
procedure:
|
||||||
|
|
|
@ -47,7 +47,6 @@ fr:
|
||||||
subject: Sujet
|
subject: Sujet
|
||||||
message: Message
|
message: Message
|
||||||
send_mail: Envoyer le message
|
send_mail: Envoyer le message
|
||||||
procedure: Démarches
|
|
||||||
new_tab: "Nouvel onglet"
|
new_tab: "Nouvel onglet"
|
||||||
helpers:
|
helpers:
|
||||||
procedure:
|
procedure:
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
"@vitejs/plugin-legacy": "^4.0.3",
|
"@vitejs/plugin-legacy": "^4.0.3",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.15",
|
||||||
"axe-core": "^4.7.2",
|
"axe-core": "^4.8.2",
|
||||||
"del-cli": "^5.1.0",
|
"del-cli": "^5.1.0",
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.48.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe MainNavigation::AnnouncesLinkComponent, type: :component do
|
||||||
|
let(:user) { build(:user) }
|
||||||
|
let!(:admin_release_note) { create(:release_note, released_on: Date.yesterday, categories: ["administrateur"]) }
|
||||||
|
let!(:instructeur_release_note) { create(:release_note, released_on: Date.yesterday, categories: ["instructeur"]) }
|
||||||
|
let(:not_published_release_note) { create(:release_note, published: false, released_on: Date.tomorrow) }
|
||||||
|
|
||||||
|
let(:as_administrateur) { false }
|
||||||
|
let(:as_instructeur) { false }
|
||||||
|
|
||||||
|
before do
|
||||||
|
if as_administrateur
|
||||||
|
user.build_administrateur
|
||||||
|
end
|
||||||
|
|
||||||
|
if as_instructeur
|
||||||
|
user.build_instructeur
|
||||||
|
end
|
||||||
|
|
||||||
|
allow(controller).to receive(:current_user).and_return(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { render_inline(described_class.new) }
|
||||||
|
|
||||||
|
context 'when signed as simple user' do
|
||||||
|
it 'does not render the announcements link if not signed in' do
|
||||||
|
expect(subject.to_html).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no signed in' do
|
||||||
|
let(:current_user) { nil }
|
||||||
|
|
||||||
|
it 'does not render the announcements link if not signed in' do
|
||||||
|
expect(subject.to_html).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when instructeur signed in' do
|
||||||
|
let(:as_instructeur) { true }
|
||||||
|
|
||||||
|
it 'renders the announcements link' do
|
||||||
|
expect(subject).to have_link("Nouveautés")
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are new announcements' do
|
||||||
|
before do
|
||||||
|
user.announces_seen_at = 5.days.ago
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render the notification badge' do
|
||||||
|
expect(subject).to have_link("Nouveautés")
|
||||||
|
expect(subject).to have_css(".notifications")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are no new announcements' do
|
||||||
|
before do
|
||||||
|
user.announces_seen_at = 1.minute.ago
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not render the notification badge' do
|
||||||
|
expect(subject).to have_link("Nouveautés")
|
||||||
|
expect(subject).not_to have_css(".notifications")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are no announcement at all' do
|
||||||
|
let(:instructeur_release_note) { nil }
|
||||||
|
|
||||||
|
it 'does not render anything' do
|
||||||
|
expect(subject.to_html).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when administrateur signed in' do
|
||||||
|
let(:as_administrateur) { true }
|
||||||
|
|
||||||
|
it 'renders the announcements link' do
|
||||||
|
expect(subject).to have_link("Nouveautés")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,93 @@
|
||||||
|
describe MainNavigation::InstructeurExpertNavigationComponent, type: :component do
|
||||||
|
let(:component) { described_class.new }
|
||||||
|
let(:as_instructeur) { true }
|
||||||
|
let(:as_expert) { false }
|
||||||
|
let(:controller_name) { 'dossiers' }
|
||||||
|
let(:user) { build(:user) }
|
||||||
|
|
||||||
|
subject { render_inline(component) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
if as_instructeur
|
||||||
|
user.build_instructeur
|
||||||
|
end
|
||||||
|
|
||||||
|
if as_expert
|
||||||
|
user.build_expert
|
||||||
|
end
|
||||||
|
|
||||||
|
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
|
||||||
|
allow_any_instance_of(ApplicationController).to receive(:administrateur_signed_in?).and_return(false)
|
||||||
|
allow_any_instance_of(ApplicationController).to receive(:controller_name).and_return(controller_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when instructor is signed in' do
|
||||||
|
it 'renders a link to instructeur procedures with current page class' do
|
||||||
|
expect(subject).to have_link('Démarches', href: component.helpers.instructeur_procedures_path)
|
||||||
|
expect(subject).to have_selector('a[aria-current="page"]', text: 'Démarches')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not have Avis' do
|
||||||
|
expect(subject).not_to have_link('Avis')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when instructor is also an expert' do
|
||||||
|
let(:as_expert) { true }
|
||||||
|
before do
|
||||||
|
allow(user.expert).to receive(:avis_summary).and_return({ unanswered: 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'render have Avis link' do
|
||||||
|
expect(subject).to have_link('Avis', href: component.helpers.expert_all_avis_path)
|
||||||
|
expect(subject).not_to have_selector('a[aria-current="page"]', text: 'Avis')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are release notes' do
|
||||||
|
let!(:release_note) { create(:release_note, categories: ['instructeur']) }
|
||||||
|
|
||||||
|
it 'renders a link to Announces page' do
|
||||||
|
expect(subject).to have_link('Nouveautés')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when expert is signed in' do
|
||||||
|
let(:as_instructeur) { false }
|
||||||
|
let(:as_expert) { true }
|
||||||
|
|
||||||
|
let(:unanswered) { 0 }
|
||||||
|
let(:controller_name) { 'avis' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(user.expert).to receive(:avis_summary).and_return({ unanswered: })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders a link to expert all avis with current page class' do
|
||||||
|
expect(subject).to have_link('Avis', href: component.helpers.expert_all_avis_path)
|
||||||
|
expect(subject).to have_selector('a[aria-current="page"]', text: 'Avis')
|
||||||
|
expect(subject).not_to have_selector('span.badge')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not have Démarches link' do
|
||||||
|
expect(subject).not_to have_link('Démarches')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are unanswered avis' do
|
||||||
|
let(:unanswered) { 2 }
|
||||||
|
|
||||||
|
it 'renders an unanswered avis badge for the expert' do
|
||||||
|
expect(subject).to have_selector('span.badge.warning', text: '2')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when expert is also instructor' do
|
||||||
|
let(:as_instructeur) { true }
|
||||||
|
|
||||||
|
it 'render have Démarches link' do
|
||||||
|
expect(subject).to have_link('Démarches', href: component.helpers.instructeur_procedures_path)
|
||||||
|
expect(subject).not_to have_selector('a[aria-current="page"]', text: 'Démarches')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -47,5 +47,31 @@ RSpec.describe ReleaseNotesController, type: :controller do
|
||||||
it { is_expected.to be_redirection }
|
it { is_expected.to be_redirection }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'touch user announces_seen_at' do
|
||||||
|
let(:user) { create(:user, administrateur: build(:administrateur)) }
|
||||||
|
|
||||||
|
context 'when default categories' do
|
||||||
|
it 'touch announces_seen_at' do
|
||||||
|
expect { subject }.to change { user.reload.announces_seen_at }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current announces_seen_at is more recent than last announce' do
|
||||||
|
before { user.update(announces_seen_at: 1.second.ago) }
|
||||||
|
|
||||||
|
it 'does not touch announces_seen_at' do
|
||||||
|
expect { subject }.not_to change { user.reload.announces_seen_at }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when specific categories' do
|
||||||
|
subject { get :index, params: { categories: ['administrateur', 'instructeur'] } }
|
||||||
|
|
||||||
|
it 'does not touch announces_seen_at' do
|
||||||
|
expect { subject }.not_to change { user.reload.announces_seen_at }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
19
spec/helpers/release_notes_helper_spec.rb
Normal file
19
spec/helpers/release_notes_helper_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
RSpec.describe ReleaseNotesHelper, type: :helper do
|
||||||
|
describe "#render_release_note_content" do
|
||||||
|
let(:release_note) { build(:release_note) }
|
||||||
|
|
||||||
|
it "adds noreferrer, noopener, and target to absolute links" do
|
||||||
|
release_note.body = "Go to <a href='http://example.com'>Example</a>"
|
||||||
|
processed_content = helper.render_release_note_content(release_note.body)
|
||||||
|
|
||||||
|
expect(processed_content.body.to_s).to include('Go to <a href="http://example.com" rel="noreferrer noopener" target="_blank" title="example.com — Nouvel onglet">Example</a>')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles content without links" do
|
||||||
|
release_note.body = "No links here"
|
||||||
|
processed_content = helper.render_release_note_content(release_note.body)
|
||||||
|
|
||||||
|
expect(processed_content.body.to_s).to include("No links here")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -27,6 +27,18 @@ describe 'wcag rules for usager', js: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def expect_axe_clean_without_main_navigation
|
||||||
|
# On page without main navigation content (like anonymous home page),
|
||||||
|
# there are either a bug in axe, either dsfr markup is not conform to wcag2a.
|
||||||
|
# There is no issue on pages having a child navigation.
|
||||||
|
expect(page).to be_axe_clean.excluding("#modal-header__menu")
|
||||||
|
expect(page).to be_axe_clean.within("#modal-header__menu").skipping("aria-prohibited-attr")
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples "axe clean without main navigation" do
|
||||||
|
it { expect_axe_clean_without_main_navigation }
|
||||||
|
end
|
||||||
|
|
||||||
context 'pages without the need to be logged in' do
|
context 'pages without the need to be logged in' do
|
||||||
before do
|
before do
|
||||||
visit path
|
visit path
|
||||||
|
@ -34,14 +46,14 @@ describe 'wcag rules for usager', js: true do
|
||||||
|
|
||||||
context 'homepage' do
|
context 'homepage' do
|
||||||
let(:path) { root_path }
|
let(:path) { root_path }
|
||||||
it { expect(page).to be_axe_clean }
|
it_behaves_like "axe clean without main navigation"
|
||||||
it_behaves_like "external links have title says it opens in a new tab"
|
it_behaves_like "external links have title says it opens in a new tab"
|
||||||
it_behaves_like "aria-label do not mix with title attribute"
|
it_behaves_like "aria-label do not mix with title attribute"
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'sign_up page' do
|
context 'sign_up page' do
|
||||||
let(:path) { new_user_registration_path }
|
let(:path) { new_user_registration_path }
|
||||||
it { expect(page).to be_axe_clean }
|
it_behaves_like "axe clean without main navigation"
|
||||||
it_behaves_like "external links have title says it opens in a new tab"
|
it_behaves_like "external links have title says it opens in a new tab"
|
||||||
it_behaves_like "aria-label do not mix with title attribute"
|
it_behaves_like "aria-label do not mix with title attribute"
|
||||||
end
|
end
|
||||||
|
@ -54,11 +66,11 @@ describe 'wcag rules for usager', js: true do
|
||||||
|
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
click_button 'Créer un compte'
|
click_button 'Créer un compte'
|
||||||
expect(page).to be_axe_clean
|
expect_axe_clean_without_main_navigation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'sign_upc confirmation' do
|
context 'sign_up confirmation' do
|
||||||
let(:path) { user_confirmation_path("user[email]" => "some@email.com") }
|
let(:path) { user_confirmation_path("user[email]" => "some@email.com") }
|
||||||
|
|
||||||
it_behaves_like "external links have title says it opens in a new tab"
|
it_behaves_like "external links have title says it opens in a new tab"
|
||||||
|
@ -67,21 +79,21 @@ describe 'wcag rules for usager', js: true do
|
||||||
|
|
||||||
context 'sign_in page' do
|
context 'sign_in page' do
|
||||||
let(:path) { new_user_session_path }
|
let(:path) { new_user_session_path }
|
||||||
it { expect(page).to be_axe_clean.excluding '#user_email' }
|
it_behaves_like "axe clean without main navigation"
|
||||||
it_behaves_like "external links have title says it opens in a new tab"
|
it_behaves_like "external links have title says it opens in a new tab"
|
||||||
it_behaves_like "aria-label do not mix with title attribute"
|
it_behaves_like "aria-label do not mix with title attribute"
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'contact page' do
|
context 'contact page' do
|
||||||
let(:path) { contact_path }
|
let(:path) { contact_path }
|
||||||
it { expect(page).to be_axe_clean }
|
it_behaves_like "axe clean without main navigation"
|
||||||
it_behaves_like "external links have title says it opens in a new tab"
|
it_behaves_like "external links have title says it opens in a new tab"
|
||||||
it_behaves_like "aria-label do not mix with title attribute"
|
it_behaves_like "aria-label do not mix with title attribute"
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'commencer page' do
|
context 'commencer page' do
|
||||||
let(:path) { commencer_path(path: procedure.path) }
|
let(:path) { commencer_path(path: procedure.path) }
|
||||||
it { expect(page).to be_axe_clean }
|
it_behaves_like "axe clean without main navigation"
|
||||||
it_behaves_like "external links have title says it opens in a new tab"
|
it_behaves_like "external links have title says it opens in a new tab"
|
||||||
it_behaves_like "aria-label do not mix with title attribute"
|
it_behaves_like "aria-label do not mix with title attribute"
|
||||||
end
|
end
|
||||||
|
@ -90,7 +102,7 @@ describe 'wcag rules for usager', js: true do
|
||||||
visit commencer_path(path: procedure.reload.path)
|
visit commencer_path(path: procedure.reload.path)
|
||||||
|
|
||||||
page.find("#help-menu_button").click
|
page.find("#help-menu_button").click
|
||||||
expect(page).to be_axe_clean
|
expect_axe_clean_without_main_navigation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ describe 'layouts/_header', type: :view do
|
||||||
allow(view).to receive(:multiple_devise_profile_connect?).and_return(false)
|
allow(view).to receive(:multiple_devise_profile_connect?).and_return(false)
|
||||||
allow(view).to receive(:instructeur_signed_in?).and_return((profile == :instructeur))
|
allow(view).to receive(:instructeur_signed_in?).and_return((profile == :instructeur))
|
||||||
allow(view).to receive(:current_instructeur).and_return(current_instructeur)
|
allow(view).to receive(:current_instructeur).and_return(current_instructeur)
|
||||||
|
allow(view).to receive(:administrateur_signed_in?).and_return(false)
|
||||||
|
allow(view).to receive(:expert_signed_in?).and_return(false)
|
||||||
allow(view).to receive(:localization_enabled?).and_return(false)
|
allow(view).to receive(:localization_enabled?).and_return(false)
|
||||||
|
|
||||||
if user
|
if user
|
||||||
|
@ -57,6 +59,7 @@ describe 'layouts/_header', type: :view do
|
||||||
let(:user) { instructeur.user }
|
let(:user) { instructeur.user }
|
||||||
let(:profile) { :instructeur }
|
let(:profile) { :instructeur }
|
||||||
let(:current_instructeur) { instructeur }
|
let(:current_instructeur) { instructeur }
|
||||||
|
let!(:release_note) { create(:release_note, categories: ['instructeur']) }
|
||||||
|
|
||||||
it { is_expected.to have_css(".fr-header__logo") }
|
it { is_expected.to have_css(".fr-header__logo") }
|
||||||
it { is_expected.to have_selector(:button, user.email, class: "account-btn") }
|
it { is_expected.to have_selector(:button, user.email, class: "account-btn") }
|
||||||
|
|
|
@ -2987,10 +2987,10 @@ aws4@^1.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||||
|
|
||||||
axe-core@^4.7.2:
|
axe-core@^4.8.2:
|
||||||
version "4.7.2"
|
version "4.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae"
|
||||||
integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==
|
integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==
|
||||||
|
|
||||||
babel-plugin-polyfill-corejs2@^0.3.3:
|
babel-plugin-polyfill-corejs2@^0.3.3:
|
||||||
version "0.3.3"
|
version "0.3.3"
|
||||||
|
|
Loading…
Add table
Reference in a new issue