diff --git a/Gemfile b/Gemfile index b9c56883dbda7de87582b0912769a6551ad00cbf..e70f6e4b9da84687df3a484591384a51bb5e9b59 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem 'i18n_data' gem 'cancancan' gem 'simple_form' gem 'simple_form_password_with_hints' +gem 'simple_form_image_fields', path: '../simple_form_image_fields' gem 'enum_help' gem 'enum-i18n' gem 'country_select' diff --git a/Gemfile.lock b/Gemfile.lock index 1f41739290f683afceaa5fd151a644bb9199e0fd..6f7f5e6e9f0a32a18728f5a3538393d5addd9294 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,6 +9,13 @@ GIT randexp rotp (>= 4.0.0) +PATH + remote: ../simple_form_image_fields + specs: + simple_form_image_fields (0.0.2) + rails + simple_form + GEM remote: https://rubygems.org/ specs: @@ -402,8 +409,8 @@ DEPENDENCIES sib-api-v3-sdk simple-navigation simple_form + simple_form_image_fields! simple_form_password_with_hints - sort_alphabetical spring two_factor_authentication! tzinfo-data diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index ab6a73603f15a33bdf65a91a05bc952cd9d5dfb1..8b8031a4195e12d84f55c2f2add2828de08edbf8 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,4 +1,6 @@ //= require jquery3 //= require jquery_ujs +//= require notyf/notyf.min //= require simple_form_password_with_hints //= require appstack/app +//= require_tree ./admin diff --git a/app/assets/javascripts/admin/notyf.js b/app/assets/javascripts/admin/notyf.js new file mode 100644 index 0000000000000000000000000000000000000000..a70f7aa75bfbf3fa12b6bb773485c4d8ab6be41d --- /dev/null +++ b/app/assets/javascripts/admin/notyf.js @@ -0,0 +1,31 @@ +/*global Notyf */ +var notyfAlerts = document.getElementsByClassName('js-notyf-alert'), + notyfNotices = document.getElementsByClassName('js-notyf-notice'), + notyf = new Notyf(); + +if (notyfAlerts.length > 0) { + notyf.open({ + type: 'error', + position: { + x: 'right', + y: 'top' + }, + message: notyfAlerts[0].innerHTML, + duration: 9000, + ripple: true, + dismissible: true + }); +} +if (notyfNotices.length > 0) { + notyf.open({ + type: 'success', + position: { + x: 'right', + y: 'top' + }, + message: notyfNotices[0].innerHTML, + duration: 9000, + ripple: true, + dismissible: true + }); +} diff --git a/app/assets/javascripts/admin/user_menu.js b/app/assets/javascripts/admin/user_menu.js new file mode 100644 index 0000000000000000000000000000000000000000..c57d6cfbf0bb350760700c13e5cddcec53109e28 --- /dev/null +++ b/app/assets/javascripts/admin/user_menu.js @@ -0,0 +1,6 @@ +/*global $ */ +$('.js-user-button').click(function (e) { + 'use strict'; + e.stopPropagation(); + $('.js-user-dropdown-toggle').dropdown('toggle'); +}); diff --git a/app/assets/stylesheets/admin.sass b/app/assets/stylesheets/admin.sass index 9a243d739ed6b82bee2aaa253cd886a04c095245..9d74d3b7236e3e064d132dbb42b604994e92976f 100644 --- a/app/assets/stylesheets/admin.sass +++ b/app/assets/stylesheets/admin.sass @@ -1,4 +1,5 @@ -@import 'appstack/light' +@import 'notyf/notyf.min' @import 'simple_form_password_with_hints' @import 'commons/*' @import 'admin/*' +@import 'appstack/light' diff --git a/app/assets/stylesheets/application/layout.sass b/app/assets/stylesheets/application/layout.sass index 16ae7bbad0f8b94f976cfdc05e3ed56f217ba244..5e912ccbf0ec75611bcc73c6d0785175dfca77e5 100644 --- a/app/assets/stylesheets/application/layout.sass +++ b/app/assets/stylesheets/application/layout.sass @@ -1,2 +1,7 @@ footer margin-top: 100px + +.alert + padding: .95rem + &-danger + color: #82322F diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 779a6029ca4b17c8b65f0d99d4fbca60ba840c9c..cebe8199652dc48218a85548bce8d70439c0cb0d 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -4,13 +4,31 @@ class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_sign_up_params, only: :create before_action :configure_account_update_params, only: :update + def update + # to prevent cognitive complexity (the bottom block should be in an if condition where password present) + # Password not provided when user from sso + params[:user][:password] ||= '' + + if params[:user][:password].empty? + params[:user].delete(:password) + else + resource.reset_password(params[:user][:password], params[:user][:password]) + end + + super + end + protected + def update_resource(resource, params) + resource.update(params) + end + def configure_sign_up_params - devise_parameter_sanitizer.permit(:sign_up, keys: [:language_id, :first_name, :last_name]) + devise_parameter_sanitizer.permit(:sign_up, keys: [:language_id, :first_name, :last_name, :picture, :picture_delete]) end def configure_account_update_params - devise_parameter_sanitizer.permit(:account_update, keys: [:mobile_phone, :language_id]) + devise_parameter_sanitizer.permit(:account_update, keys: [:mobile_phone, :language_id, :first_name, :last_name, :picture, :picture_delete]) end end diff --git a/app/models/user.rb b/app/models/user.rb index 12c38e59d26612c6a3517280436e368cd7ab4ed2..70045a197e7a05e0cc3a86719ad94a67ac1ef216 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -59,7 +59,7 @@ class User < ApplicationRecord belongs_to :university belongs_to :language has_one :researcher, class_name: 'Research::Researcher' - has_one_attached :picture + has_one_attached_deletable :picture scope :ordered, -> { order(:last_name, :first_name) } diff --git a/app/views/admin/application/_top.html.erb b/app/views/admin/application/_top.html.erb index ec95c6a8af2a9d353ab850f083e8292f26903e38..a7941becc208c77f4673f0d804eaf22e3570752b 100644 --- a/app/views/admin/application/_top.html.erb +++ b/app/views/admin/application/_top.html.erb @@ -6,7 +6,7 @@ <%= render_breadcrumbs builder: Appstack::BreadcrumbsOnRailsBuilder %> <ul class="navbar-nav navbar-align"> <li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle d-none d-sm-inline-block" href="#" data-bs-toggle="dropdown"> + <a class="nav-link dropdown-toggle d-none d-sm-inline-block js-user-dropdown-toggle" href="#" data-bs-toggle="dropdown"> <span class="text-dark"><%= current_user %></span> </a> <div class="dropdown-menu dropdown-menu-end"> @@ -15,8 +15,12 @@ </div> </li> <li> - <a class="nav-link nav-link--last" href="#"> - <%= image_tag 'avatar.jpg', class: 'avatar img-fluid rounded-circle' %> + <a class="nav-link nav-link--last js-user-button" href="#"> + <% if current_user.picture.attached? && current_user.picture.variable? %> + <%= image_tag current_user.picture.variant(resize: '40x40'), class: 'avatar img-fluid rounded-circle' %> + <% else %> + <%= image_tag 'avatar.jpg', class: 'avatar img-fluid rounded-circle' %> + <% end %> </a> </li> </ul> diff --git a/app/views/admin/layouts/application.html.erb b/app/views/admin/layouts/application.html.erb index 59d9365337a0b6e1c0d48f8b768623d7b0e8dad2..9f49453feef6018e7215150193661e3d93e166ea 100644 --- a/app/views/admin/layouts/application.html.erb +++ b/app/views/admin/layouts/application.html.erb @@ -12,6 +12,18 @@ <%= favicon_link_tag 'favicon.png' %> </head> <body data-layout="fluid" data-sidebar-position="left"> + <div class="toasts-container" style="position: fixed; top: 20px; right: 20px; z-index: 100000;"> + <% unless notice.nil? %> + <div class="js-notyf-notice d-none"> + <%= notice %> + </div> + <% end %> + <% unless alert.nil? %> + <div class="js-notyf-alert d-none"> + <%= alert %> + </div> + <% end %> + </div> <div class="wrapper"> <%= render 'admin/application/nav' %> <div class="main"> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 2e6a1b6cd533f9cecd3503c52a790c335c44bc10..b6da316412ad7e065cf5350ada71b76ef7a79077 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -20,17 +20,16 @@ <%= f.input :mobile_phone %> <%= f.association :language, include_blank: false %> + <%= f.input :picture, + as: :single_deletable_file, + input_html: { accept: '.png' } %> + <%= f.input :password, hint: t(".leave_blank_if_you_don_t_want_to_change_it"), required: false, input_html: { autocomplete: "new-password" } %> - <%= f.input :password_confirmation, - required: false, - input_html: { autocomplete: "new-password" } %> - <%= f.input :current_password, - hint: t(".we_need_your_current_password_to_confirm_your_changes"), - required: true, - input_html: { autocomplete: "current-password" } %> + + </div> <div class="form-actions"> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 7592267994d6a0f1247dc3accdbc5bfe11cca3aa..f72cf237af8d39437d273b4c57718fd13d8be581 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -15,16 +15,30 @@ required: true, input_html: { autocomplete: "email" } %> <%= f.input :password, + as: :password_with_hints, required: true, - hint: (t('devise.shared.minimum_password_length', count: @minimum_password_length) if @minimum_password_length), + allow_password_uncloaking: true, + validators: { + length: Devise.password_length.first, + uppercase_char: true, + lowercase_char: true, + numeric_char: true, + special_char: Rails.application.config.allowed_special_chars + }, input_html: { autocomplete: "new-password" } %> <%= f.input :password_confirmation, + as: :password_with_sync, required: true, + allow_password_uncloaking: true, + compare_with_field: :password, input_html: { autocomplete: "new-password" } %> <%= f.association :language, required: true, label_method: lambda { |l| I18nData.languages(I18n.locale.to_s.upcase)[l.iso_code.to_s.upcase].capitalize }, include_blank: 'Sélectionnez une langue' %> + <%= f.input :picture, + as: :single_deletable_file, + input_html: { accept: '.png' } %> </div> <div class="form-actions text-center mt-3"> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 5726df05b7dfccdfaa02f973d470b85c33cbd6a5..c2009000b5c58802d3e48fd056ba82807ca6198f 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -13,6 +13,7 @@ <body> <%= render 'nav' %> <main class="container"> + <%= render_breadcrumbs builder: Appstack::BreadcrumbsOnRailsBuilder %> <%= yield %> </main> diff --git a/app/views/layouts/devise.html.erb b/app/views/layouts/devise.html.erb index df37364431c8d31fde821c69dbcb121bda12c558..c46882bdda74ee6569067b66aaf3cc77aa02185d 100644 --- a/app/views/layouts/devise.html.erb +++ b/app/views/layouts/devise.html.erb @@ -14,8 +14,8 @@ <div class="main d-flex justify-content-center w-100"> <main class="content d-flex p-0"> <div class="container d-flex flex-column"> - <% unless notice.blank? %><div class="alert alert-success mt-2" role="alert"><div class="alert-message"><%= notice.html_safe %></div></div><% end %> - <% unless alert.blank? %><div class="alert alert-danger mt-2" role="alert"><div class="alert-message"><%= alert.html_safe %></div></div><% end %> + <% unless notice.blank? %><div class="alert alert-success mt-2" role="alert"><%= notice.html_safe %></div><% end %> + <% unless alert.blank? %><div class="alert alert-danger mt-2" role="alert"><%= alert.html_safe %></div><% end %> <div class="row h-100"> <div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100"> diff --git a/config/locales/en.yml b/config/locales/en.yml index 56dec7625e7904b2e9ed2ab15503693d90c6f27d..1a81b6369aae25f6960a81070bb0ee49761564ab 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -74,6 +74,8 @@ en: subtitle: Sign in to your account to continue please-confirm: Are you sure? simple_form: + error_notification: + default_message: "Please review the problems below:" hints: user: mobile_phone: "International format (+XX)" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1ee02d4dd3c44f37b145c265ff7aeb43aa5c95da..20c631a121de08e675ad38ca610cc4c76d2ae8c6 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -74,6 +74,8 @@ fr: subtitle: Vous devez être authentifié pour continuer please-confirm: Est-ce que vous confirmez ? simple_form: + error_notification: + default_message: "Les erreurs ci-dessous empêchent la validation :" hints: user: mobile_phone: "Format international (+XX)" diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..587e1568842c4caee453f500facc75fb3cbe709a --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "notyf": "^3.10.0" + } +} diff --git a/yarn.lock b/yarn.lock index fb57ccd13afbd082ad82051c2ffebef4840661ec..a4bd56507f4daa37ac7fa233e781f35cdf13290b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,3 +2,7 @@ # yarn lockfile v1 +notyf@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/notyf/-/notyf-3.10.0.tgz#67a64443c69ea0e6495c56ea0f91198860163d06" + integrity sha512-Mtnp+0qiZxgrH+TzVlzhWyZceHdAZ/UWK0/ju9U0HQeDpap1mZ8cC7H5wSI5mwgni6yeAjaxsTw0sbMK+aSuHw==