diff --git a/Gemfile b/Gemfile index e886ddb8b..f9b3fe818 100644 --- a/Gemfile +++ b/Gemfile @@ -113,6 +113,7 @@ group :test do gem 'rubocop-rspec_rails' # RSpec for unit testing gem 'rspec' + gem 'rspec-rebound' # RSpec Rails integration gem 'rspec-rails' # Selenium WebDriver for browser automation diff --git a/Gemfile.lock b/Gemfile.lock index 3ffb09ed8..ae1a0f239 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -642,6 +642,8 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) + rspec-rebound (0.2.1) + rspec-core (~> 3.3) rspec-support (3.13.4) rswag (2.16.0) rswag-api (= 2.16.0) @@ -851,6 +853,7 @@ DEPENDENCIES redis (~> 5.4) rspec rspec-rails + rspec-rebound rswag rubocop rubocop-capybara diff --git a/app/controllers/better_together/settings_controller.rb b/app/controllers/better_together/settings_controller.rb new file mode 100644 index 000000000..c7f5e4d17 --- /dev/null +++ b/app/controllers/better_together/settings_controller.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module BetterTogether + class SettingsController < ApplicationController + end +end diff --git a/app/controllers/better_together/users/registrations_controller.rb b/app/controllers/better_together/users/registrations_controller.rb index 70600bcdc..e93f4d637 100644 --- a/app/controllers/better_together/users/registrations_controller.rb +++ b/app/controllers/better_together/users/registrations_controller.rb @@ -3,11 +3,62 @@ module BetterTogether module Users # Override default Devise registrations controller - class RegistrationsController < ::Devise::RegistrationsController + class RegistrationsController < ::Devise::RegistrationsController # rubocop:todo Metrics/ClassLength include DeviseLocales skip_before_action :check_platform_privacy before_action :set_required_agreements, only: %i[new create] + before_action :configure_account_update_params, only: [:update] + + # PUT /resource + # We need to use a copy of the resource because we don't want to change + # the current user in place. + def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength + self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key) + prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email) + + resource_updated = update_resource(resource, account_update_params) + yield resource if block_given? + if resource_updated + set_flash_message_for_update(resource, prev_unconfirmed_email) + bypass_sign_in resource, scope: resource_name if sign_in_after_change_password? + + respond_to do |format| + format.html { respond_with resource, location: after_update_path_for(resource) } + format.turbo_stream do + flash.now[:notice] = I18n.t('devise.registrations.updated') + render turbo_stream: [ + turbo_stream.replace( + 'flash_messages', + partial: 'layouts/better_together/flash_messages', + locals: { flash: } + ), + turbo_stream.replace( + 'account-settings', + partial: 'devise/registrations/edit_form' + ) + ] + end + end + else + clean_up_passwords resource + set_minimum_password_length + + respond_to do |format| + format.html { respond_with resource, location: after_update_path_for(resource) } + format.turbo_stream do + render turbo_stream: [ + turbo_stream.replace('form_errors', partial: 'layouts/better_together/errors', + locals: { object: resource }), + turbo_stream.replace( + 'account-settings', + partial: 'devise/registrations/edit_form' + ) + ] + end + end + end + end def new super do |user| @@ -62,6 +113,15 @@ def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize protected + def account_update_params + devise_parameter_sanitizer.sanitize(:account_update) + end + + def configure_account_update_params + devise_parameter_sanitizer.permit(:account_update, + keys: %i[email password password_confirmation current_password]) + end + def set_required_agreements @privacy_policy_agreement = BetterTogether::Agreement.find_by(identifier: 'privacy_policy') @terms_of_service_agreement = BetterTogether::Agreement.find_by(identifier: 'terms_of_service') @@ -84,6 +144,10 @@ def after_inactive_sign_up_path_for(resource) super end + def after_update_path_for(_resource) + better_together.edit_user_registration_path + end + def person_params params.require(:user).require(:person_attributes).permit(%i[identifier name description]) end diff --git a/app/policies/better_together/user_policy.rb b/app/policies/better_together/user_policy.rb index 11c31508a..c576f9753 100644 --- a/app/policies/better_together/user_policy.rb +++ b/app/policies/better_together/user_policy.rb @@ -3,11 +3,11 @@ module BetterTogether class UserPolicy < ApplicationPolicy # rubocop:todo Style/Documentation def index? - user.present? + permitted_to?('manage_platform') end def show? - user.present? + user.present? && (record == user || permitted_to?('manage_platform')) end def create? @@ -19,7 +19,7 @@ def new? end def update? - false + permitted_to?('manage_platform') end def edit? @@ -36,7 +36,9 @@ def me? class Scope < Scope # rubocop:todo Style/Documentation def resolve - scope.all + return scope.where(id: user.id) unless permitted_to?('manage_platform') + + scope.order(created_at: :desc) end end end diff --git a/app/views/better_together/people/_form.html.erb b/app/views/better_together/people/_form.html.erb index b8000d4ed..434ec9eef 100644 --- a/app/views/better_together/people/_form.html.erb +++ b/app/views/better_together/people/_form.html.erb @@ -41,21 +41,15 @@ diff --git a/app/views/better_together/settings/index.html.erb b/app/views/better_together/settings/index.html.erb new file mode 100644 index 000000000..6cb31febb --- /dev/null +++ b/app/views/better_together/settings/index.html.erb @@ -0,0 +1,124 @@ + +<% content_for :page_title, t('.title') %> + +
+
+
+
+

+ + <%= t('.title') %> +

+
+
+
+ +
+ +
+ +
+ + +
+ + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/app/views/devise/registrations/_edit_form.html.erb b/app/views/devise/registrations/_edit_form.html.erb new file mode 100644 index 000000000..4a7c9ce67 --- /dev/null +++ b/app/views/devise/registrations/_edit_form.html.erb @@ -0,0 +1,60 @@ +<%= turbo_frame_tag "account-settings" do %> +
+
+
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name, format: :turbo_stream), html: { method: :put, class: 'needs-validation', novalidate: true, data: { turbo_frame: "account-settings", controller: "better_together--form-validation" } }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> +
+ +
+ <%= f.label :email, class: 'form-label' %> + <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control' %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
+ <%= t('devise.registrations.edit.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email) %> +
+ <% end %> + + +
+ <%= f.label :password, class: 'form-label' %> + (<%= t('devise.registrations.edit.leave_blank_if_you_don_t_want_to_change_it') %>) + <%= f.password_field :password, autocomplete: "new-password", class: 'form-control' %> + <% if @minimum_password_length %> + + <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %> + + <% end %> +
+ + +
+ <%= f.label :password_confirmation, class: 'form-label' %> + <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'form-control' %> +
+ + +
+ <%= required_label f, :current_password, class: 'form-label' %> + (<%= t('devise.registrations.edit.we_need_your_current_password_to_confirm_your_changes') %>) + <%= f.password_field :current_password, autocomplete: "current-password", class: 'form-control', required: true %> +
+ + +
+ <%= f.submit t('devise.registrations.edit.update'), class: 'btn btn-primary' %> +
+ <% end %> + + <% if params[:cancel] %> +

<%= t('devise.registrations.edit.cancel_my_account') %>

+
+ <%= t('devise.registrations.edit.unhappy') %> <%= button_to t('devise.registrations.edit.cancel_my_account'), registration_path(resource_name), data: { confirm: t('devise.registrations.edit.are_you_sure'), turbo_confirm: t('devise.registrations.edit.are_you_sure') }, method: :delete, class: 'btn btn-danger' %> +
+ <% end %> +
+
+
+<% end %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 8d5e0a145..5f93db08d 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -2,63 +2,4 @@ <%= t('.title', resource: devise_i18n_fix_model_name_case(resource.model_name.human, i18n_key: 'registrations.edit.title')) %> <% end %> -
-
-
-

<%= t('.title', resource: devise_i18n_fix_model_name_case(resource.model_name.human, i18n_key: 'registrations.edit.title')) %>

- - <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'needs-validation', novalidate: true }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - - -
- <%= f.label :email, class: 'form-label' %> - <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control' %> -
- - <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> -
- <%= t('.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email) %> -
- <% end %> - - -
- <%= f.label :password, class: 'form-label' %> - (<%= t('.leave_blank_if_you_don_t_want_to_change_it') %>) - <%= f.password_field :password, autocomplete: "new-password", class: 'form-control' %> - <% if @minimum_password_length %> - - <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %> - - <% end %> -
- - -
- <%= f.label :password_confirmation, class: 'form-label' %> - <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'form-control' %> -
- - -
- <%= f.label :current_password, class: 'form-label' %> - (<%= t('.we_need_your_current_password_to_confirm_your_changes') %>) - <%= f.password_field :current_password, autocomplete: "current-password", class: 'form-control' %> -
- - -
- <%= f.submit t('.update'), class: 'btn btn-primary' %> -
- <% end %> - -

<%= t('.cancel_my_account') %>

-
- <%= t('.unhappy') %> <%= button_to t('.cancel_my_account'), registration_path(resource_name), data: { confirm: t('.are_you_sure'), turbo_confirm: t('.are_you_sure') }, method: :delete, class: 'btn btn-danger' %> -
- - <%= link_to t('devise.shared.links.back'), :back, class: 'btn btn-secondary' %> -
-
-
+<%= render 'edit_form' %> diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb index 0e5adf560..0a00d2a93 100644 --- a/app/views/devise/shared/_error_messages.html.erb +++ b/app/views/devise/shared/_error_messages.html.erb @@ -1,5 +1,5 @@ <% if resource.errors.any? %> -
+