tech(refactor): identity blocks

This commit is contained in:
mfo 2024-04-12 17:59:50 +02:00
parent e5b6a28e0b
commit 95de0d6239
19 changed files with 180 additions and 174 deletions

View file

@ -0,0 +1,11 @@
class Dossiers::IndividualFormComponent < ApplicationComponent
delegate :for_tiers?, to: :@dossier
def initialize(dossier:)
@dossier = dossier
end
def email_notifications?(individual)
individual.object.notification_method == Individual.notification_methods[:email]
end
end

View file

@ -0,0 +1,7 @@
---
en:
self_title: Your identity
callout_text: "You are acting as a proxy for a principal, either professionally (such as accountant, lawyer, civil servant…) or personally (family). Make sure to respect the conditions of"
callout_link: Articles 1984 and following of the Civil Code.
callout_link_title: Articles 1984 and following of the Civil Code
beneficiaire_title: "Identity of the beneficiary"

View file

@ -0,0 +1,7 @@
---
fr:
self_title: Votre identité
callout_text: Vous agissez en tant que mandataire, soit professionnellement (comme expert-comptable, avocat, agent public…) soit personnellement (famille). Assurez-vous de respecter les conditions
callout_link: des Articles 1984 et suivants du Code civil.
callout_link_title: Articles 1984 et suivants du Code civil
beneficiaire_title: Identité du bénéficiaire

View file

@ -0,0 +1,76 @@
= form_for @dossier, url: update_identite_dossier_path(@dossier), html: { id: 'identite-form', class: "form", "data-controller" => "for-tiers" } do |f|
- if for_tiers?
.fr-alert.fr-alert--info.fr-mb-2w
%p.fr-notice__text
= t('.callout_text')
= link_to(t('.callout_link'),
'https://www.legifrance.gouv.fr/codes/section_lc/LEGITEXT000006070721/LEGISCTA000006136404/#LEGISCTA000006136404',
title: helpers.new_tab_suffix(t('.callout_link_title')),
**helpers.external_link_attributes)
%fieldset.fr-fieldset.mandataire-infos
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
%h2.fr-h4
= t('.self_title')
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: f, attribute: :mandataire_first_name, opts: { autocomplete: 'given-name' })
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: f, attribute: :mandataire_last_name, opts: { autocomplete: 'family-name' })
= f.fields_for :individual, include_id: false do |individual|
%fieldset.fr-fieldset.fr-mb-0.individual-infos
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
%h2.fr-h4
- if for_tiers?
= t('.beneficiaire_title')
- else
= t('.self_title')
.fr-fieldset__element.fr-mb-0
%fieldset.fr-fieldset
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
= t('activerecord.attributes.individual.gender')
= render EditableChamp::AsteriskMandatoryComponent.new
.fr-fieldset__element
.fr-radio-group
= individual.radio_button :gender, Individual::GENDER_FEMALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_FEMALE}"
%label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_FEMALE}" }
= Individual.human_attribute_name('gender.female')
.fr-fieldset__element
.fr-radio-group
= individual.radio_button :gender, Individual::GENDER_MALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_MALE}"
%label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_MALE}" }
= Individual.human_attribute_name('gender.male')
.fr-fieldset__element.fr-mb-0
%fieldset.fr-fieldset.width-100
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: individual, attribute: :prenom, opts: { autocomplete: (for_tiers? ? false : 'given-name') })
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: individual, attribute: :nom, opts: { autocomplete: (for_tiers? ? false : 'family-name') })
- if @dossier.procedure.ask_birthday?
.fr-fieldset__element
= render Dsfr::InputComponent.new(form: individual, attribute: :birthdate, input_type: :date_field,
opts: { placeholder: 'Format : AAAA-MM-JJ', max: Date.today.iso8601, min: "1900-01-01", autocomplete: 'bday' })
- if for_tiers?
%fieldset.fr-fieldset
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
= t('activerecord.attributes.individual.notification_method')
= render EditableChamp::AsteriskMandatoryComponent.new
- Individual.notification_methods.each do |method, _|
.fr-fieldset__element
.fr-radio-group
= individual.radio_button :notification_method, method, id: "notification_method_#{method}", "data-action" => "for-tiers#toggleEmailInput", "data-for-tiers-target" => "notificationMethod"
%label.fr-label{ for: "notification_method_#{method}" }
= t("activerecord.attributes.individual.notification_methods.#{method}")
.fr-fieldset__element.fr-fieldset__element--short-text{ "data-for-tiers-target" => "emailContainer", class: class_names(hidden: !email_notifications?(individual)) }
= render Dsfr::InputComponent.new(form: individual, attribute: :email, input_type: :email_field, opts: { "data-for-tiers-target" => "emailInput" })
= f.submit t('views.users.dossiers.identite.continue'), class: "fr-btn"

View file

@ -135,6 +135,13 @@ module Users
@dossier = dossier
@user = current_user
@no_description = true
respond_to do |format|
format.html
format.turbo_stream do
@dossier.update_columns(params.require(:dossier).permit(:for_tiers).to_h)
end
end
end
def update_identite

View file

@ -1,87 +1,32 @@
import { toggle } from '@utils';
import { ApplicationController } from './application_controller';
function onVisibleEnableInputs(element: HTMLInputElement) {
element.disabled = false;
element.required = true;
}
function onHiddenDisableInputs(element: HTMLInputElement) {
element.disabled = true;
element.required = false;
}
export class ForTiersController extends ApplicationController {
static targets = [
'mandataireFirstName',
'mandataireLastName',
'forTiers',
'mandataireBlock',
'beneficiaireNotificationBlock',
'email',
'notificationMethod',
'mandataireTitle',
'beneficiaireTitle',
'emailInput'
];
static targets = ['emailContainer', 'emailInput', 'notificationMethod'];
declare mandataireFirstNameTarget: HTMLInputElement;
declare mandataireLastNameTarget: HTMLInputElement;
declare forTiersTargets: NodeListOf<HTMLInputElement>;
declare mandataireBlockTarget: HTMLElement;
declare beneficiaireNotificationBlockTarget: HTMLElement;
declare notificationMethodTargets: NodeListOf<HTMLInputElement>;
declare emailTarget: HTMLInputElement;
declare mandataireTitleTarget: HTMLElement;
declare beneficiaireTitleTarget: HTMLElement;
declare emailInput: HTMLInputElement;
declare emailContainerTarget: HTMLElement;
declare emailInputTarget: HTMLInputElement;
connect() {
const emailInputElement = this.emailTarget.querySelector('input');
if (emailInputElement) {
this.emailInput = emailInputElement;
}
this.toggleFieldRequirements();
this.addAllEventListeners();
}
addAllEventListeners() {
this.forTiersTargets.forEach((radio) => {
radio.addEventListener('change', () => this.toggleFieldRequirements());
});
this.notificationMethodTargets.forEach((radio) => {
radio.addEventListener('change', () => this.toggleEmailInput());
});
}
toggleFieldRequirements() {
const forTiersSelected = this.isForTiersSelected();
this.toggleDisplay(this.mandataireBlockTarget, forTiersSelected);
this.toggleDisplay(
this.beneficiaireNotificationBlockTarget,
forTiersSelected
);
this.mandataireFirstNameTarget.required = forTiersSelected;
this.mandataireLastNameTarget.required = forTiersSelected;
this.mandataireTitleTarget.classList.toggle('hidden', forTiersSelected);
this.beneficiaireTitleTarget.classList.toggle('hidden', !forTiersSelected);
this.notificationMethodTargets.forEach((radio) => {
radio.required = forTiersSelected;
});
this.toggleEmailInput();
}
isForTiersSelected() {
return Array.from(this.forTiersTargets).some(
(radio) => radio.checked && radio.value === 'true'
);
}
toggleDisplay(element: HTMLElement, shouldDisplay: boolean) {
element.classList.toggle('hidden', !shouldDisplay);
}
toggleEmailInput() {
const isEmailSelected = this.isEmailSelected();
const forTiersSelected = this.isForTiersSelected();
if (this.emailInput) {
this.emailInput.required = forTiersSelected && isEmailSelected;
toggle(this.emailContainerTarget, isEmailSelected);
if (!isEmailSelected) {
this.emailInput.value = '';
}
this.toggleDisplay(this.emailTarget, forTiersSelected && isEmailSelected);
if (isEmailSelected) {
onVisibleEnableInputs(this.emailInputTarget);
} else {
onHiddenDisableInputs(this.emailInputTarget);
}
}

View file

@ -3,7 +3,7 @@
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }
- if !dossier_submission_is_closed?(@dossier)
= form_for @dossier, url: update_identite_dossier_path(@dossier), html: { class: "form", "data-controller" => "for-tiers" } do |f|
= form_for @dossier, url: identite_dossier_path(@dossier), method: :patch, html: { class: "form" }, data: {turbo: true, controller: :autosubmit} do |f|
%fieldset#radio-rich-hint.fr-fieldset{ "aria-labelledby" => "radio-rich-hint-legend radio-rich-hint-messages" }
%legend#radio-rich-hint-legend.fr-fieldset__legend--regular.fr-fieldset__legend
@ -11,90 +11,19 @@
.fr-fieldset__element
.fr-radio-group.fr-radio-rich
= f.radio_button :for_tiers, false, required: true, id: "radio-self-manage", "data-action" => "click->for-tiers#toggleFieldRequirements", "data-for-tiers-target" => "forTiers"
= f.radio_button :for_tiers, false, required: true, id: "radio-self-manage"
%label.fr-label{ for: "radio-self-manage" }
= t('activerecord.attributes.dossier.for_tiers.false')
.fr-radio-rich__img
%span.fr-icon-user-fill
.fr-fieldset__element
.fr-radio-group.fr-radio-rich
= f.radio_button :for_tiers, true, required: true, id: "radio-tiers-manage", "data-action" => "click->for-tiers#toggleFieldRequirements", "data-for-tiers-target" => "forTiers"
= f.radio_button :for_tiers, true, required: true, id: "radio-tiers-manage"
%label.fr-label{ for: "radio-tiers-manage" }
= t('activerecord.attributes.dossier.for_tiers.true')
.fr-radio-rich__img
%span.fr-icon-parent-fill
.mandataire-infos{ "data-for-tiers-target" => "mandataireBlock" }
.fr-alert.fr-alert--info.fr-mb-2w
%p.fr-notice__text
= t('views.users.dossiers.identite.callout_text')
= link_to(t('views.users.dossiers.identite.callout_link'),
'https://www.legifrance.gouv.fr/codes/section_lc/LEGITEXT000006070721/LEGISCTA000006136404/#LEGISCTA000006136404',
title: new_tab_suffix(t('views.users.dossiers.identite.callout_link_title')),
**external_link_attributes)
= f.submit t('views.users.dossiers.identite.continue'), class: 'visually-hidden'
%fieldset.fr-fieldset
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
%h2.fr-h4= t('views.users.dossiers.identite.self_title')
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: f, attribute: :mandataire_first_name, opts: { "data-for-tiers-target" => "mandataireFirstName" })
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: f, attribute: :mandataire_last_name, opts: { "data-for-tiers-target" => "mandataireLastName" })
= f.fields_for :individual, include_id: false do |individual|
.individual-infos
%fieldset.fr-fieldset
%legend.fr-fieldset__legend--regular.fr-fieldset__legend{ "data-for-tiers-target" => "mandataireTitle" }
%h2.fr-h4= t('views.users.dossiers.identite.self_title')
%legend.fr-fieldset__legend--regular.fr-fieldset__legend.hidden{ "data-for-tiers-target" => "beneficiaireTitle" }
%h2.fr-h4= t('views.users.dossiers.identite.beneficiaire_title')
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
= t('activerecord.attributes.individual.gender')
= render EditableChamp::AsteriskMandatoryComponent.new
.fr-fieldset__element
.fr-radio-group
= individual.radio_button :gender, Individual::GENDER_FEMALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_FEMALE}"
%label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_FEMALE}" }
= Individual.human_attribute_name('gender.female')
.fr-fieldset__element
.fr-radio-group
= individual.radio_button :gender, Individual::GENDER_MALE, required: true, id: "identite_champ_radio_#{Individual::GENDER_MALE}"
%label.fr-label{ for: "identite_champ_radio_#{Individual::GENDER_MALE}" }
= Individual.human_attribute_name('gender.male')
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: individual, attribute: :prenom, opts: { autocomplete: 'given-name' })
.fr-fieldset__element.fr-fieldset__element--short-text
= render Dsfr::InputComponent.new(form: individual, attribute: :nom, opts: { autocomplete: 'family-name' })
%fieldset.fr-fieldset{ "data-for-tiers-target" => "beneficiaireNotificationBlock" }
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
= t('activerecord.attributes.individual.notification_method')
= render EditableChamp::AsteriskMandatoryComponent.new
- Individual.notification_methods.each do |method, _|
.fr-fieldset__element
.fr-radio-group
= individual.radio_button :notification_method, method, id: "notification_method_#{method}", "data-action" => "for-tiers#toggleFieldRequirements", "data-for-tiers-target" => "notificationMethod"
%label.fr-label{ for: "notification_method_#{method}" }
= t("activerecord.attributes.individual.notification_methods.#{method}")
.fr-fieldset__element.fr-fieldset__element--short-text.hidden{ "data-for-tiers-target" => "email" }
= render Dsfr::InputComponent.new(form: individual, attribute: :email)
- if @dossier.procedure.ask_birthday?
.fr-fieldset__element
= render Dsfr::InputComponent.new(form: individual, attribute: :birthdate, input_type: :date_field,
opts: { placeholder: 'Format : AAAA-MM-JJ', max: Date.today.iso8601, min: "1900-01-01", autocomplete: 'bday' })
= f.submit t('views.users.dossiers.identite.continue'), class: "fr-btn"
= render Dossiers::IndividualFormComponent.new(dossier: @dossier)

View file

@ -0,0 +1 @@
= turbo_stream.replace 'identite-form', render(Dossiers::IndividualFormComponent.new(dossier: @dossier))

View file

@ -423,11 +423,6 @@ en:
archived_dossier: "Your file will be kept %{duree_conservation_dossiers_dans_ds} more months"
identite:
legend: 'This file is:'
self_title: Your identity
callout_text: "You are acting as a proxy for a principal, either professionally (such as accountant, lawyer, civil servant…) or personally (family). Make sure to respect the conditions of"
callout_link: Articles 1984 and following of the Civil Code.
callout_link_title: Articles 1984 and following of the Civil Code
beneficiaire_title: "Identity of the beneficiary"
identity_siret: Identify your establishment
civility: Civility
first_name: First Name

View file

@ -426,11 +426,6 @@ fr:
archived_dossier: "Votre dossier sera conservé %{duree_conservation_dossiers_dans_ds} mois supplémentaire"
identite:
legend: 'Ce dossier est : '
self_title: Votre identité
callout_text: Vous agissez en tant que mandataire, soit professionnellement (comme expert-comptable, avocat, agent public…) soit personnellement (famille). Assurez-vous de respecter les conditions
callout_link: des Articles 1984 et suivants du Code civil.
callout_link_title: Articles 1984 et suivants du Code civil
beneficiaire_title: Identité du bénéficiaire
identity_siret: Identifier votre établissement
civility: Civilité
first_name: Prénom

View file

@ -361,6 +361,7 @@ Rails.application.routes.draw do
resources :dossiers, only: [:index, :show, :destroy, :new] do
member do
get 'identite'
patch 'identite'
patch 'update_identite'
post 'clone'
get 'siret'

View file

@ -8,7 +8,9 @@ shared_examples "the user has got a prefilled dossier, owned by themselves" do
expect(page).to have_field('Prénom', with: prenom_value)
expect(page).to have_field('Nom', with: nom_value)
end
click_on 'Continuer'
within "#identite-form" do
click_on 'Continuer'
end
expect(page).to have_current_path(brouillon_dossier_path(dossier))
expect(page).to have_field(type_de_champ_text.libelle, with: text_value)

View file

@ -126,7 +126,9 @@ describe 'wcag rules for usager', js: true do
fill_in('Prénom', with: 'prenom')
fill_in('Nom', with: 'nom')
end
click_on 'Continuer'
within "#identite-form" do
click_on 'Continuer'
end
expect(page).to be_axe_clean
end

View file

@ -272,7 +272,9 @@ describe 'fetch API Particulier Data', js: true do
fill_in('Nom', with: 'nom')
end
click_button('Continuer')
within "#identite-form" do
click_on 'Continuer'
end
fill_in 'Le numéro dallocataire CAF', with: numero_allocataire
fill_in 'Le code postal', with: 'wrong_code'
@ -331,7 +333,9 @@ describe 'fetch API Particulier Data', js: true do
fill_in('Prénom', with: 'Georges')
fill_in('Nom', with: 'Moustaki')
end
click_button('Continuer')
within "#identite-form" do
click_on 'Continuer'
end
fill_in "Identifiant", with: 'wrong code'
@ -405,7 +409,9 @@ describe 'fetch API Particulier Data', js: true do
fill_in('Prénom', with: 'Angela Claire Louise')
fill_in('Nom', with: 'Dubois')
end
click_button('Continuer')
within "#identite-form" do
click_on 'Continuer'
end
fill_in "INE", with: 'wrong code'
@ -469,7 +475,9 @@ describe 'fetch API Particulier Data', js: true do
fill_in('Prénom', with: 'Karine')
fill_in('Nom', with: 'FERRI')
end
click_button('Continuer')
within "#identite-form" do
click_on 'Continuer'
end
fill_in 'Le numéro fiscal', with: numero_fiscal
fill_in "La référence davis dimposition", with: 'wrong_code'

View file

@ -258,7 +258,9 @@ describe 'The routing with rules', js: true do
find('label', text: 'Monsieur').click
fill_in('Prénom', with: 'prenom', visible: true)
fill_in('Nom', with: 'Nom', visible: true)
click_button('Continuer')
within "#identite-form" do
click_button('Continuer')
end
# the old system should not be present
expect(page).not_to have_selector("#dossier_groupe_instructeur_id")

View file

@ -665,7 +665,9 @@ describe 'The user' do
find('label', text: 'Monsieur').click
fill_in('Prénom', with: 'prenom', visible: true)
fill_in('Nom', with: 'Nom', visible: true)
click_on 'Continuer'
within "#identite-form" do
click_on 'Continuer'
end
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
end
end

View file

@ -28,7 +28,9 @@ describe 'Creating a new dossier:', js: true do
shared_examples 'the user can create a new draft' do
it do
click_button('Continuer')
within "#identite-form" do
click_button('Continuer')
end
expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last))
expect(user.dossiers.first.individual.birthdate).to eq(expected_birthday)
@ -58,7 +60,6 @@ describe 'Creating a new dossier:', js: true do
context 'when individual fill dossier for a tiers' do
it 'completes the form with email notification method selected' do
find('label', text: 'Pour un bénéficiaire : membre de la famille, proche, mandant, professionnel en charge du suivi du dossier…').click
within('.mandataire-infos') do
fill_in('Prénom', with: 'John')
fill_in('Nom', with: 'Doe')
@ -73,7 +74,15 @@ describe 'Creating a new dossier:', js: true do
find('label', text: 'Par e-mail').click
fill_in('dossier_individual_attributes_email', with: 'prenom.nom@mail.com')
click_button('Continuer')
find('label', text: 'Monsieur').click # force focus out
within "#identite-form" do
within '.suspect-email' do
expect(page).to have_content("Information : Voulez-vous dire ?")
click_button("Oui")
end
click_button("Continuer")
end
expect(procedure.dossiers.last.individual.notification_method == 'email')
expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last))
end
@ -93,7 +102,10 @@ describe 'Creating a new dossier:', js: true do
end
find('label', text: 'Pas de notification').click
click_button('Continuer')
within "#identite-form" do
click_button('Continuer')
end
expect(procedure.dossiers.last.individual.notification_method.empty?)
expect(page).to have_current_path(brouillon_dossier_path(procedure.dossiers.last))
end

View file

@ -79,7 +79,9 @@ describe 'dropdown list with other option activated', js: true do
fill_in('Prénom', with: 'prenom')
fill_in('Nom', with: 'nom')
end
click_on 'Continuer'
within "#identite-form" do
click_on 'Continuer'
end
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
end
end

View file

@ -85,7 +85,9 @@ describe 'linked dropdown lists' do
fill_in('Prénom', with: 'prenom')
fill_in('Nom', with: 'nom')
end
click_on 'Continuer'
within "#identite-form" do
click_on 'Continuer'
end
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
end
end