diff --git a/app/components/procedure/invitation_with_typo_component.rb b/app/components/procedure/invitation_with_typo_component.rb new file mode 100644 index 000000000..bec642cfa --- /dev/null +++ b/app/components/procedure/invitation_with_typo_component.rb @@ -0,0 +1,21 @@ +class Procedure::InvitationWithTypoComponent < ApplicationComponent + def initialize(maybe_typo:, url:, params:, title:) + @maybe_typo = maybe_typo + @url = url + @params = params + @title = title + end + + def render? + @maybe_typo.present? + end + + def maybe_typos + email_checker = EmailChecker.new + + @maybe_typo.map do |actual_email| + suggested_email = email_checker.check(email: actual_email)[:email_suggestions].first + [actual_email, suggested_email] + end + end +end diff --git a/app/components/procedure/invitation_with_typo_component/invitation_with_typo_component.html.haml b/app/components/procedure/invitation_with_typo_component/invitation_with_typo_component.html.haml new file mode 100644 index 000000000..12fdcc1dd --- /dev/null +++ b/app/components/procedure/invitation_with_typo_component/invitation_with_typo_component.html.haml @@ -0,0 +1,10 @@ += render Dsfr::AlertComponent.new(title: "nous pensons avoir identifié une faute de frappe : ", state: :warning, extra_class_names: 'fr-mb-3w') do |c| + - c.with_body do + %p= @title + %ul + - maybe_typos.each do |(actual_email, suggested_email)| + %li + = "Je confirme " + = button_to "#{actual_email}", @url, method: :POST, params: @params.call(actual_email), class: 'fr-btn fr-btn--tertiary fr-btn--sm', form: {class: 'inline'} + = " ou " + = button_to "#{suggested_email}", @url, method: :POST, params: @params.call(suggested_email), class: 'fr-btn fr-btn--tertiary fr-btn--sm', form: {class: 'inline'} diff --git a/app/controllers/administrateurs/experts_procedures_controller.rb b/app/controllers/administrateurs/experts_procedures_controller.rb index 1a59bcba5..d606e0821 100644 --- a/app/controllers/administrateurs/experts_procedures_controller.rb +++ b/app/controllers/administrateurs/experts_procedures_controller.rb @@ -1,25 +1,31 @@ module Administrateurs class ExpertsProceduresController < AdministrateurController + include EmailSanitizableConcern before_action :retrieve_procedure + before_action :retrieve_experts_procedure, only: [:index, :create] + before_action :retrieve_experts_emails, only: [:index, :create] def index - @experts_procedure = @procedure - .experts_procedures - .where(revoked_at: nil) - .sort_by { |expert_procedure| expert_procedure.expert.email } - @experts_emails = experts_procedure_emails end def create emails = params['emails'].presence || [].to_json - emails = JSON.parse(emails).map(&:strip).map(&:downcase) + @maybe_typo, emails = JSON.parse(emails) + .map { EmailSanitizer.sanitize(_1) } + .partition { EmailChecker.new.check(email: _1)[:email_suggestions].present? } + errors = if !@maybe_typo.empty? + ["Attention, nous pensons avoir identifié une faute de frappe dans les invitations : #{@maybe_typo.join(', ')}"] + else + [] + end + emails += [EmailSanitizer.sanitize(params['maybe_typo'])] if params['maybe_typo'].present? valid_users, invalid_users = emails .map { |email| User.create_or_promote_to_expert(email, SecureRandom.hex) } .partition(&:valid?) if invalid_users.any? - flash[:alert] = invalid_users + errors += invalid_users .filter { |user| user.errors.present? } .map { |user| "#{user.email} : #{user.errors.full_messages_for(:email).join(', ')}" } end @@ -37,7 +43,9 @@ module Administrateurs value: valid_users.map(&:email).join(', '), procedure: @procedure.id) end - redirect_to admin_procedure_experts_path(@procedure) + + flash[:alert] = errors.join(". ") if !errors.empty? + render :index end def update @@ -57,8 +65,12 @@ module Administrateurs private - def experts_procedure_emails - @procedure.experts.map(&:email).sort + def retrieve_experts_procedure + @experts_procedure ||= @procedure.experts_procedures.where(revoked_at: nil).sort_by { _1.expert.email } + end + + def retrieve_experts_emails + @experts_emails ||= @experts_procedure.map { _1.expert.email } end def expert_procedure_params diff --git a/app/views/administrateurs/experts_procedures/index.html.haml b/app/views/administrateurs/experts_procedures/index.html.haml index dc8909f82..daad54134 100644 --- a/app/views/administrateurs/experts_procedures/index.html.haml +++ b/app/views/administrateurs/experts_procedures/index.html.haml @@ -58,6 +58,7 @@ - if @procedure.experts_require_administrateur_invitation? .card + = render Procedure::InvitationWithTypoComponent.new(maybe_typo: @maybe_typo, url: admin_procedure_experts_path(@procedure), params: ->(email) { { maybe_typo: email } }, title: "Avant d'ajouter l'email à la liste d'expert prédéfinie, veuillez confirmer" ) = form_for :experts_procedure, url: admin_procedure_experts_path(@procedure), html: { class: 'form' } do |f| diff --git a/spec/controllers/administrateurs/experts_procedures_controller_spec.rb b/spec/controllers/administrateurs/experts_procedures_controller_spec.rb index 6a9ca33d5..46d753ae8 100644 --- a/spec/controllers/administrateurs/experts_procedures_controller_spec.rb +++ b/spec/controllers/administrateurs/experts_procedures_controller_spec.rb @@ -21,23 +21,45 @@ describe Administrateurs::ExpertsProceduresController, type: :controller do describe '#create' do let(:expert) { create(:expert) } let(:expert2) { create(:expert) } + let(:procedure) { create :procedure, administrateur: admin, experts_require_administrateur_invitation: true } - subject do - post :create, params: { - procedure_id: procedure.id, - emails: "[\"#{expert.email}\",\"#{expert2.email}\"]" - } + subject { post :create, params: params } + before { subject } + + context 'when inviting multiple valid experts' do + let(:params) { { procedure_id: procedure.id, emails: [expert.email, expert2.email].to_json } } + + it 'creates experts' do + expect(procedure.experts.include?(expert)).to be_truthy + expect(procedure.experts.include?(expert2)).to be_truthy + expect(flash.notice).to be_present + expect(assigns(:maybe_typo)).to eq([]) + expect(response).to have_http_status(:success) + end end - before do - subject + context 'when inviting expert using an email with typos' do + let(:params) { { procedure_id: procedure.id, emails: ['martin@oraneg.fr'].to_json } } + render_views + it 'warns' do + expect(flash.alert).to be_present + expect(assigns(:maybe_typo)).to eq(['martin@oraneg.fr']) + expect(response).to have_http_status(:success) + end end - context 'of multiple experts' do - it { expect(procedure.experts.include?(expert)).to be_truthy } - it { expect(procedure.experts.include?(expert2)).to be_truthy } - it { expect(flash.notice).to be_present } - it { expect(response).to redirect_to(admin_procedure_experts_path(procedure)) } + context 'when forcing email with typos' do + let(:maybe_typo) { 'martin@oraneg.fr' } + let(:params) { { procedure_id: procedure.id, maybe_typo: } } + + it 'works' do + created_user = User.where(email: maybe_typo).first + expect(created_user).to be_an_instance_of(User) + expect(created_user.expert).to be_an_instance_of(Expert) + expect(procedure.experts.include?(created_user.expert)).to be_truthy + expect(flash.notice).to be_present + expect(response).to have_http_status(:success) + end end end