diff --git a/app/controllers/france_connect/particulier_controller.rb b/app/controllers/france_connect/particulier_controller.rb index 13f41b6f1..5817a3185 100644 --- a/app/controllers/france_connect/particulier_controller.rb +++ b/app/controllers/france_connect/particulier_controller.rb @@ -53,7 +53,7 @@ class FranceConnect::ParticulierController < ApplicationController if !user.can_france_connect? flash.alert = t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path) - render js: ajax_redirect(root_path) + redirect_to root_path else @fci.update(user: user) @fci.delete_merge_token! @@ -63,8 +63,6 @@ class FranceConnect::ParticulierController < ApplicationController end else flash.alert = t('france_connect.particulier.flash.invalid_password') - - render js: helpers.render_flash end end @@ -112,10 +110,7 @@ class FranceConnect::ParticulierController < ApplicationController if @fci.nil? || !@fci.valid_for_merge? flash.alert = t('france_connect.particulier.flash.merger_token_expired', application_name: APPLICATION_NAME) - respond_to do |format| - format.html { redirect_to root_path } - format.js { render js: ajax_redirect(root_path) } - end + redirect_to root_path end end @@ -134,12 +129,7 @@ class FranceConnect::ParticulierController < ApplicationController user.update_attribute('loged_in_with_france_connect', User.loged_in_with_france_connects.fetch(:particulier)) - redirection_location = stored_location_for(current_user) || root_path(current_user) - - respond_to do |format| - format.html { redirect_to redirection_location } - format.js { render js: ajax_redirect(root_path) } - end + redirect_to stored_location_for(current_user) || root_path(current_user) end def redirect_france_connect_error_connection diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b6f91df67..c45b81052 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -40,77 +40,6 @@ module ApplicationHelper tag.div(**html.merge(data: { controller: 'react', react_component_value: name, react_props_value: props.to_json })) end - def render_to_element(selector, partial:, outer: false, locals: {}) - method = outer ? 'outerHTML' : 'innerHTML' - html = escape_javascript(render partial: partial, locals: locals) - # rubocop:disable Rails/OutputSafety - raw("document.querySelector('#{selector}').#{method} = \"#{html}\";") - # rubocop:enable Rails/OutputSafety - end - - def append_to_element(selector, partial:, locals: {}) - html = escape_javascript(render partial: partial, locals: locals) - # rubocop:disable Rails/OutputSafety - raw("document.querySelector('#{selector}').insertAdjacentHTML('beforeend', \"#{html}\");") - # rubocop:enable Rails/OutputSafety - end - - def render_flash(timeout: false, sticky: false, fixed: false) - if flash.any? - html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky, fixed: fixed }, outer: true) - flash.clear - if timeout - html += remove_element('#flash_messages', timeout: timeout, inner: true) - end - html - end - end - - def remove_element(selector, timeout: 0, inner: false) - script = "(function() {"; - script << "var el = document.querySelector('#{selector}');" - method = (inner ? "el.innerHTML = ''" : "el.parentNode.removeChild(el)") - if timeout.present? && timeout > 0 - script << "if (el) { setTimeout(function() { #{method}; }, #{timeout}); }" - else - script << "if (el) { #{method} };" - end - script << "})();" - # rubocop:disable Rails/OutputSafety - raw(script); - # rubocop:enable Rails/OutputSafety - end - - def show_element(selector) - # rubocop:disable Rails/OutputSafety - raw("document.querySelector('#{selector}').classList.remove('hidden');") - # rubocop:enable Rails/OutputSafety - end - - def focus_element(selector) - # rubocop:disable Rails/OutputSafety - raw("document.querySelector('#{selector}').focus();") - # rubocop:enable Rails/OutputSafety - end - - def disable_element(selector) - # rubocop:disable Rails/OutputSafety - raw("document.querySelector('#{selector}').disabled = true;") - # rubocop:enable Rails/OutputSafety - end - - def enable_element(selector) - # rubocop:disable Rails/OutputSafety - raw("document.querySelector('#{selector}').disabled = false;") - # rubocop:enable Rails/OutputSafety - end - - def fire_event(event_name, data) - # rubocop:disable Rails/OutputSafety - raw("DS.fire('#{event_name}', #{raw(data)});") - # rubocop:enable Rails/OutputSafety - end - def current_email current_user&.email || current_instructeur&.email || diff --git a/app/javascript/entrypoints/application.js b/app/javascript/entrypoints/application.js index 6e3ec2bc6..3143e2dd9 100644 --- a/app/javascript/entrypoints/application.js +++ b/app/javascript/entrypoints/application.js @@ -5,17 +5,14 @@ import { Application } from '@hotwired/stimulus'; import '@gouvfr/dsfr/dist/dsfr.module.js'; import '../shared/activestorage/ujs'; -import '../shared/remote-poller'; -import '../shared/safari-11-file-xhr-workaround'; +import '../shared/safari-11-empty-file-workaround'; import '../shared/toggle-target'; -import '../shared/ujs-error-handling'; import { registerControllers } from '../shared/stimulus-loader'; import '../new_design/form-validation'; import '../new_design/procedure-context'; import '../new_design/procedure-form'; -import '../new_design/spinner'; import '../new_design/support'; import { @@ -31,11 +28,7 @@ import { acceptEmailSuggestion, discardEmailSuggestionBox } from '../new_design/user-sign_up'; -import { - showFusion, - showNewAccount, - showNewAccountPasswordConfirmation -} from '../new_design/fc-fusion'; +import { showFusion, showNewAccount } from '../new_design/fc-fusion'; const application = Application.start(); registerControllers(application); @@ -49,7 +42,6 @@ const DS = { showImportJustificatif, showFusion, showNewAccount, - showNewAccountPasswordConfirmation, replaceSemicolonByComma, acceptEmailSuggestion, discardEmailSuggestionBox diff --git a/app/javascript/new_design/fc-fusion.js b/app/javascript/new_design/fc-fusion.js index e9d4b7628..3ef0fce4e 100644 --- a/app/javascript/new_design/fc-fusion.js +++ b/app/javascript/new_design/fc-fusion.js @@ -3,17 +3,11 @@ import { show, hide } from '@utils'; export function showFusion() { show(document.querySelector('.fusion')); hide(document.querySelector('.new-account')); - hide(document.querySelector('.new-account-password-confirmation')); + hide(document.querySelector('#new-account-password-confirmation')); } export function showNewAccount() { hide(document.querySelector('.fusion')); show(document.querySelector('.new-account')); - hide(document.querySelector('.new-account-password-confirmation')); -} - -export function showNewAccountPasswordConfirmation() { - hide(document.querySelector('.fusion')); - hide(document.querySelector('.new-account')); - show(document.querySelector('.new-account-password-confirmation')); + hide(document.querySelector('#new-account-password-confirmation')); } diff --git a/app/javascript/new_design/spinner.js b/app/javascript/new_design/spinner.js deleted file mode 100644 index 8b1fbac3e..000000000 --- a/app/javascript/new_design/spinner.js +++ /dev/null @@ -1,13 +0,0 @@ -import { show, hide, delegate } from '@utils'; - -function showSpinner() { - [...document.querySelectorAll('.spinner')].forEach(show); -} - -function hideSpinner() { - [...document.querySelectorAll('.spinner')].forEach(hide); -} - -delegate('ajax:complete', '[data-spinner]', hideSpinner); -delegate('ajax:stopped', '[data-spinner]', hideSpinner); -delegate('ajax:send', '[data-spinner]', showSpinner); diff --git a/app/javascript/shared/remote-poller.js b/app/javascript/shared/remote-poller.js deleted file mode 100644 index 77984c900..000000000 --- a/app/javascript/shared/remote-poller.js +++ /dev/null @@ -1,101 +0,0 @@ -import { httpRequest, delegate } from '@utils'; - -addEventListener('DOMContentLoaded', () => { - attachementPoller.deactivate(); - exportPoller.deactivate(); - - const attachments = document.querySelectorAll('[data-attachment-poll-url]'); - const exports = document.querySelectorAll('[data-export-poll-url]'); - - for (let { dataset } of attachments) { - attachementPoller.add(dataset.attachmentPollUrl); - } - - for (let { dataset } of exports) { - exportPoller.add(dataset.exportPollUrl); - } -}); - -addEventListener('attachment:update', ({ detail: { url } }) => { - attachementPoller.add(url); -}); - -addEventListener('export:update', ({ detail: { url } }) => { - exportPoller.add(url); -}); - -delegate('click', '[data-attachment-refresh]', (event) => { - event.preventDefault(); - attachementPoller.check(); -}); - -// Periodically check the state of a set of URLs. -// -// Each time the given URL is requested, the matching `show.js.erb` view is rendered, -// causing the state to be refreshed. -// -// This is used mainly to refresh attachments during the anti-virus check, -// but also to refresh the state of a pending spreadsheet export. -class RemotePoller { - urls = new Set(); - timeout; - checks = 0; - - constructor(settings = {}) { - this.interval = settings.interval; - this.maxChecks = settings.maxChecks; - } - - get isEnabled() { - return this.checks <= this.maxChecks; - } - - get isActive() { - return this.timeout !== undefined; - } - - add(url) { - if (this.isEnabled) { - if (!this.isActive) { - this.activate(); - } - this.urls.add(url); - } - } - - check() { - let urls = this.urls; - this.reset(); - for (let url of urls) { - // Start the request. The JS payload in the response will update the page. - // (Errors are ignored, because background tasks shouldn't report errors to the user.) - httpRequest(url) - .js() - .catch(() => {}); - } - } - - activate() { - clearTimeout(this.timeout); - this.timeout = setTimeout(() => { - this.checks++; - this.currentInterval = this.interval * 1.5; - this.check(); - }, this.currentInterval); - } - - deactivate() { - this.checks = 0; - this.currentInterval = this.interval; - this.reset(); - } - - reset() { - clearTimeout(this.timeout); - this.urls = new Set(); - this.timeout = undefined; - } -} - -const attachementPoller = new RemotePoller({ interval: 3000, maxChecks: 5 }); -const exportPoller = new RemotePoller({ interval: 6000, maxChecks: 10 }); diff --git a/app/javascript/shared/safari-11-empty-file-workaround.ts b/app/javascript/shared/safari-11-empty-file-workaround.ts new file mode 100644 index 000000000..19f689049 --- /dev/null +++ b/app/javascript/shared/safari-11-empty-file-workaround.ts @@ -0,0 +1,36 @@ +// iOS 11.3 Safari / macOS Safari 11.1 empty XHR bug workaround. +// This should work with every modern browser which supports ES5 (including IE9). +// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty +// https://github.com/rails/rails/issues/32440 + +document.documentElement.addEventListener( + 'turbo:before-fetch-request', + (event) => { + const target = event.target as Element; + const inputs = target.querySelectorAll( + 'input[type="file"]:not([disabled])' + ); + for (const input of inputs) { + if (input.files?.length == 0) { + input.setAttribute('data-safari-temp-disabled', 'true'); + input.setAttribute('disabled', ''); + } + } + } +); + +document.documentElement.addEventListener( + 'turbo:before-fetch-response', + (event) => { + const target = event.target as Element; + const inputs = target.querySelectorAll( + 'input[type="file"][data-safari-temp-disabled]' + ); + for (const input of inputs) { + input.removeAttribute('data-safari-temp-disabled'); + input.removeAttribute('disabled'); + } + } +); + +export {}; diff --git a/app/javascript/shared/safari-11-file-xhr-workaround.js b/app/javascript/shared/safari-11-file-xhr-workaround.js deleted file mode 100644 index 2d7fa2707..000000000 --- a/app/javascript/shared/safari-11-file-xhr-workaround.js +++ /dev/null @@ -1,26 +0,0 @@ -// iOS 11.3 Safari / macOS Safari 11.1 empty XHR bug workaround. -// This should work with every modern browser which supports ES5 (including IE9). -// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty -// https://github.com/rails/rails/issues/32440 - -document.addEventListener('ajax:before', function (e) { - let inputs = e.target.querySelectorAll('input[type="file"]:not([disabled])'); - inputs.forEach(function (input) { - if (input.files.length > 0) { - return; - } - input.setAttribute('data-safari-temp-disabled', 'true'); - input.setAttribute('disabled', ''); - }); -}); - -// You should call this by yourself when you aborted an ajax request by stopping a event in ajax:before hook. -document.addEventListener('ajax:beforeSend', function (e) { - let inputs = e.target.querySelectorAll( - 'input[type="file"][data-safari-temp-disabled]' - ); - inputs.forEach(function (input) { - input.removeAttribute('data-safari-temp-disabled'); - input.removeAttribute('disabled'); - }); -}); diff --git a/app/javascript/shared/ujs-error-handling.js b/app/javascript/shared/ujs-error-handling.js deleted file mode 100644 index 495dc0e06..000000000 --- a/app/javascript/shared/ujs-error-handling.js +++ /dev/null @@ -1,8 +0,0 @@ -// For links and requests done through rails-ujs (mostly data-remote links), -// redirect to the sign-in page when the server responds '401 Unauthorized'. -document.addEventListener('ajax:error', (event) => { - const [, , xhr] = event.detail; - if (xhr && xhr.status == 401) { - location.reload(); // reload whole page so Devise will redirect to sign-in - } -}); diff --git a/app/views/administrateurs/experts_procedures/index.html.haml b/app/views/administrateurs/experts_procedures/index.html.haml index a4c658504..68ebef395 100644 --- a/app/views/administrateurs/experts_procedures/index.html.haml +++ b/app/views/administrateurs/experts_procedures/index.html.haml @@ -16,7 +16,7 @@ url: allow_expert_review_admin_procedure_path(@procedure), html: { class: 'form procedure-form__column--form no-background' } do |f| %label.toggle-switch - = f.check_box :allow_expert_review, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()' + = f.check_box :allow_expert_review, class: 'toggle-switch-checkbox', onchange: 'this.form.requestSubmit()' %span.toggle-switch-control.round %span.toggle-switch-label.on %span.toggle-switch-label.off @@ -30,7 +30,7 @@ url: experts_require_administrateur_invitation_admin_procedure_path(@procedure), html: { class: 'form procedure-form__column--form no-background' } do |f| %label.toggle-switch - = f.check_box :experts_require_administrateur_invitation, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()' + = f.check_box :experts_require_administrateur_invitation, class: 'toggle-switch-checkbox', onchange: 'this.form.requestSubmit()' %span.toggle-switch-control.round %span.toggle-switch-label.on %span.toggle-switch-label.off @@ -76,12 +76,11 @@ %td.text-center = form_for expert_procedure, url: admin_procedure_expert_path(id: expert_procedure), - remote: true, method: :put, - authenticity_token: true, + data: { turbo: true }, html: { class: 'form procedure-form__column--form no-background' } do |f| %label.toggle-switch - = f.check_box :allow_decision_access, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()' + = f.check_box :allow_decision_access, class: 'toggle-switch-checkbox', onchange: 'this.form.requestSubmit()' %span.toggle-switch-control.round %span.toggle-switch-label.on %span.toggle-switch-label.off diff --git a/app/views/france_connect/particulier/_password_confirmation.html.haml b/app/views/france_connect/particulier/_password_confirmation.html.haml index eda3ea1bc..0fa778734 100644 --- a/app/views/france_connect/particulier/_password_confirmation.html.haml +++ b/app/views/france_connect/particulier/_password_confirmation.html.haml @@ -3,7 +3,7 @@ %br = t('.fill_in_password') -= form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form fconnect-form' do += form_tag france_connect_particulier_merge_with_existing_account_path, data: { turbo: true }, class: 'mt-2 form fconnect-form' do = hidden_field_tag :merge_token, merge_token = hidden_field_tag :email, email = label_tag :password, t('views.registrations.new.password_label', min_length: 8) diff --git a/app/views/france_connect/particulier/merge.html.haml b/app/views/france_connect/particulier/merge.html.haml index ce8fedec8..aeb4ec2bb 100644 --- a/app/views/france_connect/particulier/merge.html.haml +++ b/app/views/france_connect/particulier/merge.html.haml @@ -19,9 +19,9 @@ .fusion.hidden %p= t('.title_fill_in_password') - = form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form fconnect-form' do - = hidden_field_tag :merge_token, @fci.merge_token - = hidden_field_tag :email, @fci.email_france_connect + = form_tag france_connect_particulier_merge_with_existing_account_path, data: { turbo: true }, class: 'mt-2 form fconnect-form' do + = hidden_field_tag :merge_token, @fci.merge_token, id: dom_id(@fci, :fusion_merge_token) + = hidden_field_tag :email, @fci.email_france_connect, id: dom_id(@fci, :fusion_email) = label_tag :password, t('views.registrations.new.password_label', min_length: 8) = password_field_tag :password, nil, autocomplete: 'current-password', class: 'mb-1' @@ -36,11 +36,11 @@ .new-account.hidden %p= t('.title_fill_in_email', application_name: APPLICATION_NAME) - = form_tag france_connect_particulier_merge_with_new_account_path, remote: true, class: 'mt-2 form' do - = hidden_field_tag :merge_token, @fci.merge_token - = label_tag :email, t('views.registrations.new.email_label') - = email_field_tag :email, "", required: true + = form_tag france_connect_particulier_merge_with_new_account_path, data: { turbo: true }, class: 'mt-2 form' do + = hidden_field_tag :merge_token, @fci.merge_token, id: dom_id(@fci, :new_account_merge_token) + = label_tag :email, t('views.registrations.new.email_label'), for: dom_id(@fci, :new_account_email) + = email_field_tag :email, "", required: true, id: dom_id(@fci, :new_account_email) = submit_tag t('.button_use_this_email'), class: 'button primary' - .new-account-password-confirmation.hidden + #new-account-password-confirmation.hidden diff --git a/app/views/france_connect/particulier/merge_with_existing_account.turbo_stream.haml b/app/views/france_connect/particulier/merge_with_existing_account.turbo_stream.haml new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/france_connect/particulier/merge_with_new_account.js.erb b/app/views/france_connect/particulier/merge_with_new_account.js.erb deleted file mode 100644 index cea1ca67a..000000000 --- a/app/views/france_connect/particulier/merge_with_new_account.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= render_to_element('.new-account-password-confirmation', partial: 'password_confirmation', locals: { email: @email, merge_token: @merge_token }) %> - -DS.showNewAccountPasswordConfirmation(); diff --git a/app/views/france_connect/particulier/merge_with_new_account.turbo_stream.haml b/app/views/france_connect/particulier/merge_with_new_account.turbo_stream.haml new file mode 100644 index 000000000..7d14ef01a --- /dev/null +++ b/app/views/france_connect/particulier/merge_with_new_account.turbo_stream.haml @@ -0,0 +1,4 @@ += turbo_stream.update 'new-account-password-confirmation', partial: 'password_confirmation', locals: { email: @email, merge_token: @merge_token } += turbo_stream.hide_all '.fusion' += turbo_stream.hide_all '.new-account' += turbo_stream.show 'new-account-password-confirmation' diff --git a/app/views/instructeurs/archives/create.js.haml b/app/views/instructeurs/archives/create.js.haml deleted file mode 100644 index 7fe9f7f0b..000000000 --- a/app/views/instructeurs/archives/create.js.haml +++ /dev/null @@ -1 +0,0 @@ -= render_flash(sticky: true) diff --git a/spec/controllers/france_connect/particulier_controller_spec.rb b/spec/controllers/france_connect/particulier_controller_spec.rb index fd6c2cd27..76bbae30d 100644 --- a/spec/controllers/france_connect/particulier_controller_spec.rb +++ b/spec/controllers/france_connect/particulier_controller_spec.rb @@ -198,7 +198,7 @@ describe FranceConnect::ParticulierController, type: :controller do let(:merge_token) { fci.create_merge_token! } let(:email) { 'EXISTING_account@a.com ' } let(:password) { 'my-s3cure-p4ssword' } - let(:format) { :js } + let(:format) { :turbo_stream } subject { post :merge_with_existing_account, params: { merge_token: merge_token, email: email, password: password }, format: format } @@ -309,7 +309,7 @@ describe FranceConnect::ParticulierController, type: :controller do let(:fci) { FranceConnectInformation.create!(user_info) } let(:merge_token) { fci.create_merge_token! } let(:email) { ' Account@a.com ' } - let(:format) { :js } + let(:format) { :turbo_stream } subject { post :merge_with_new_account, params: { merge_token: merge_token, email: email }, format: format } @@ -323,7 +323,7 @@ describe FranceConnect::ParticulierController, type: :controller do expect(fci.user.email).to eq(email.downcase.strip) expect(fci.merge_token).to be_nil expect(controller.current_user).to eq(fci.user) - expect(response.body).to include("window.location.href='/'") + expect(response).to redirect_to(root_path) end end diff --git a/spec/system/france_connect/france_connect_particulier_spec.rb b/spec/system/france_connect/france_connect_particulier_spec.rb index 33ad0aae1..f6cad87ee 100644 --- a/spec/system/france_connect/france_connect_particulier_spec.rb +++ b/spec/system/france_connect/france_connect_particulier_spec.rb @@ -83,7 +83,7 @@ describe 'France Connect Particulier Connexion' do expect(page).to have_css('#password-for-another-account', visible: true) - within '.new-account-password-confirmation' do + within '#new-account-password-confirmation' do fill_in 'password', with: 'my-s3cure-p4ssword' click_on 'Fusionner les comptes' end