Skip to content

Commit 55700b7

Browse files
authored
Merge branch 'main' into dependabot/bundler/devise-i18n-1.15.0
2 parents b87b10b + 891871f commit 55700b7

File tree

21 files changed

+1598
-89
lines changed

21 files changed

+1598
-89
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ group :test do
113113
gem 'rubocop-rspec_rails'
114114
# RSpec for unit testing
115115
gem 'rspec'
116+
gem 'rspec-rebound'
116117
# RSpec Rails integration
117118
gem 'rspec-rails'
118119
# Selenium WebDriver for browser automation

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@ GEM
642642
rspec-expectations (~> 3.13)
643643
rspec-mocks (~> 3.13)
644644
rspec-support (~> 3.13)
645+
rspec-rebound (0.2.1)
646+
rspec-core (~> 3.3)
645647
rspec-support (3.13.4)
646648
rswag (2.16.0)
647649
rswag-api (= 2.16.0)
@@ -851,6 +853,7 @@ DEPENDENCIES
851853
redis (~> 5.4)
852854
rspec
853855
rspec-rails
856+
rspec-rebound
854857
rswag
855858
rubocop
856859
rubocop-capybara
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
module BetterTogether
4+
class SettingsController < ApplicationController
5+
end
6+
end

app/controllers/better_together/users/registrations_controller.rb

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,62 @@
33
module BetterTogether
44
module Users
55
# Override default Devise registrations controller
6-
class RegistrationsController < ::Devise::RegistrationsController
6+
class RegistrationsController < ::Devise::RegistrationsController # rubocop:todo Metrics/ClassLength
77
include DeviseLocales
88

99
skip_before_action :check_platform_privacy
1010
before_action :set_required_agreements, only: %i[new create]
11+
before_action :configure_account_update_params, only: [:update]
12+
13+
# PUT /resource
14+
# We need to use a copy of the resource because we don't want to change
15+
# the current user in place.
16+
def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
17+
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
18+
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
19+
20+
resource_updated = update_resource(resource, account_update_params)
21+
yield resource if block_given?
22+
if resource_updated
23+
set_flash_message_for_update(resource, prev_unconfirmed_email)
24+
bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?
25+
26+
respond_to do |format|
27+
format.html { respond_with resource, location: after_update_path_for(resource) }
28+
format.turbo_stream do
29+
flash.now[:notice] = I18n.t('devise.registrations.updated')
30+
render turbo_stream: [
31+
turbo_stream.replace(
32+
'flash_messages',
33+
partial: 'layouts/better_together/flash_messages',
34+
locals: { flash: }
35+
),
36+
turbo_stream.replace(
37+
'account-settings',
38+
partial: 'devise/registrations/edit_form'
39+
)
40+
]
41+
end
42+
end
43+
else
44+
clean_up_passwords resource
45+
set_minimum_password_length
46+
47+
respond_to do |format|
48+
format.html { respond_with resource, location: after_update_path_for(resource) }
49+
format.turbo_stream do
50+
render turbo_stream: [
51+
turbo_stream.replace('form_errors', partial: 'layouts/better_together/errors',
52+
locals: { object: resource }),
53+
turbo_stream.replace(
54+
'account-settings',
55+
partial: 'devise/registrations/edit_form'
56+
)
57+
]
58+
end
59+
end
60+
end
61+
end
1162

1263
def new
1364
super do |user|
@@ -62,6 +113,15 @@ def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
62113

63114
protected
64115

116+
def account_update_params
117+
devise_parameter_sanitizer.sanitize(:account_update)
118+
end
119+
120+
def configure_account_update_params
121+
devise_parameter_sanitizer.permit(:account_update,
122+
keys: %i[email password password_confirmation current_password])
123+
end
124+
65125
def set_required_agreements
66126
@privacy_policy_agreement = BetterTogether::Agreement.find_by(identifier: 'privacy_policy')
67127
@terms_of_service_agreement = BetterTogether::Agreement.find_by(identifier: 'terms_of_service')
@@ -84,6 +144,10 @@ def after_inactive_sign_up_path_for(resource)
84144
super
85145
end
86146

147+
def after_update_path_for(_resource)
148+
better_together.edit_user_registration_path
149+
end
150+
87151
def person_params
88152
params.require(:user).require(:person_attributes).permit(%i[identifier name description])
89153
end

app/policies/better_together/user_policy.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
module BetterTogether
44
class UserPolicy < ApplicationPolicy # rubocop:todo Style/Documentation
55
def index?
6-
user.present?
6+
permitted_to?('manage_platform')
77
end
88

99
def show?
10-
user.present?
10+
user.present? && (record == user || permitted_to?('manage_platform'))
1111
end
1212

1313
def create?
@@ -19,7 +19,7 @@ def new?
1919
end
2020

2121
def update?
22-
false
22+
permitted_to?('manage_platform')
2323
end
2424

2525
def edit?
@@ -36,7 +36,9 @@ def me?
3636

3737
class Scope < Scope # rubocop:todo Style/Documentation
3838
def resolve
39-
scope.all
39+
return scope.where(id: user.id) unless permitted_to?('manage_platform')
40+
41+
scope.order(created_at: :desc)
4042
end
4143
end
4244
end

app/views/better_together/people/_form.html.erb

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,15 @@
4141
<!-- Details Tab -->
4242
<div class="nav-tab-pane tab-pane fade show active" id="person-details" role="tabpanel" aria-labelledby="person-details-tab">
4343
<div class="mb-3">
44-
<%= required_label(form, :name, class: "form-label") %>
45-
<%= render partial: 'better_together/shared/translated_string_field', locals: { model: person, form: form, attribute: 'name' } %>
46-
<small class="form-text text-muted"><%= t('helpers.hint.person.name') %></small>
44+
<%= render partial: 'better_together/shared/translated_string_field', locals: { model: person, form: form, attribute: 'name' } %>
4745
</div>
4846

4947
<div class="mb-3">
50-
<%= form.label :description, class: "form-label" %>
51-
<%= render partial: 'better_together/shared/translated_rich_text_field', locals: { model: person, form: form, attribute: 'description_html' } %>
52-
<small class="form-text text-muted"><%= t('helpers.hint.person.description') %></small>
48+
<%= render partial: 'better_together/shared/translated_rich_text_field', locals: { model: person, form: form, attribute: 'description_html' } %>
5349
</div>
5450

5551
<div class="mb-3">
56-
<%= form.label :slug, class: "form-label" %>
57-
<%= render partial: 'better_together/shared/translated_string_field', locals: { model: person, form: form, attribute: 'slug' } %>
58-
<small class="form-text text-muted"><%= t('helpers.hint.person.slug') %></small>
52+
<%= render partial: 'better_together/shared/translated_string_field', locals: { model: person, form: form, attribute: 'slug' } %>
5953
</div>
6054
</div>
6155

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<!-- filepath: /home/rob/projects/better-together/community-engine-rails/app/views/better_together/settings/index.html.erb -->
2+
<% content_for :page_title, t('.title') %>
3+
4+
<div class="container-fluid mt-4" data-controller="better_together--tabs">
5+
<div class="row">
6+
<div class="col-12">
7+
<div class="d-flex justify-content-between align-items-center mb-4">
8+
<h1 class="h2 mb-0">
9+
<i class="fa-solid fa-cog me-2" aria-hidden="true"></i>
10+
<%= t('.title') %>
11+
</h1>
12+
</div>
13+
</div>
14+
</div>
15+
16+
<div class="row">
17+
<!-- Vertical Pills Navigation -->
18+
<div class="col-md-3 mb-3">
19+
<div class="nav flex-column nav-pills" id="settings-tabs" role="tablist" aria-orientation="vertical">
20+
<!-- <button class="nav-link active" id="platform-tab" data-bs-toggle="pill" data-bs-target="#platform" type="button" role="tab" aria-controls="platform" aria-selected="true" data-better_together--tabs-target="tab">
21+
<i class="fa-solid fa-server me-2" aria-hidden="true"></i>
22+
<%= t('.tabs.platform') %>
23+
</button> -->
24+
<!-- <button class="nav-link" id="personal-tab" data-bs-toggle="pill" data-bs-target="#personal" type="button" role="tab" aria-controls="personal" aria-selected="false" data-better_together--tabs-target="tab">
25+
<i class="fa-solid fa-user me-2" aria-hidden="true"></i>
26+
<%= t('.tabs.personal') %>
27+
</button> -->
28+
<button class="nav-link" id="account-tab" data-bs-toggle="pill" data-bs-target="#account" type="button" role="tab" aria-controls="account" aria-selected="false" data-better_together--tabs-target="tab">
29+
<i class="fa-solid fa-key me-2" aria-hidden="true"></i>
30+
<%= t('.tabs.account') %>
31+
</button>
32+
<!-- <button class="nav-link" id="privacy-tab" data-bs-toggle="pill" data-bs-target="#privacy" type="button" role="tab" aria-controls="privacy" aria-selected="false" data-better_together--tabs-target="tab">
33+
<i class="fa-solid fa-shield-alt me-2" aria-hidden="true"></i>
34+
<%= t('.tabs.privacy') %>
35+
</button> -->
36+
</div>
37+
</div>
38+
39+
<!-- Tab Content -->
40+
<div class="col-md-9 tab-content" id="settings-tabs-content">
41+
<!-- Platform Tab -->
42+
<div class="nav-tab-pane tab-pane fade" id="platform" role="tabpanel" aria-labelledby="platform-tab">
43+
<div class="card border-0 shadow-sm">
44+
<div class="card-body">
45+
<h3 class="card-title h5">
46+
<i class="fa-solid fa-server me-2" aria-hidden="true"></i>
47+
<%= t('.platform.title') %>
48+
</h3>
49+
<p class="text-muted mb-4"><%= t('.platform.description') %></p>
50+
51+
<!-- Platform settings content will go here -->
52+
<div class="alert alert-info" role="status">
53+
<i class="fa-solid fa-info-circle me-2" aria-hidden="true"></i>
54+
<%= t('.platform.coming_soon') %>
55+
</div>
56+
</div>
57+
</div>
58+
</div>
59+
60+
<!-- Personal Tab -->
61+
<div class="nav-tab-pane tab-pane fade" id="personal" role="tabpanel" aria-labelledby="personal-tab">
62+
<div class="card border-0 shadow-sm">
63+
<div class="card-body">
64+
<h3 class="card-title h5">
65+
<i class="fa-solid fa-user me-2" aria-hidden="true"></i>
66+
<%= t('.personal.title') %>
67+
</h3>
68+
<p class="text-muted mb-4"><%= t('.personal.description') %></p>
69+
70+
<!-- Personal settings content will go here -->
71+
<div class="alert alert-info" role="status">
72+
<i class="fa-solid fa-info-circle me-2" aria-hidden="true"></i>
73+
<%= t('.personal.coming_soon') %>
74+
</div>
75+
</div>
76+
</div>
77+
</div>
78+
79+
<!-- Account Tab -->
80+
<div class="nav-tab-pane tab-pane fade show active" id="account" role="tabpanel" aria-labelledby="account-tab">
81+
<div class="card border-0 shadow-sm">
82+
<div class="card-body">
83+
<h3 class="card-title h5">
84+
<i class="fa-solid fa-key me-2" aria-hidden="true"></i>
85+
<%= t('.account.title') %>
86+
</h3>
87+
<p class="text-muted mb-4"><%= t('.account.description') %></p>
88+
89+
<!-- Account settings content -->
90+
<%= turbo_frame_tag "account-settings",
91+
src: edit_user_registration_path,
92+
loading: "lazy",
93+
class: "d-block" do %>
94+
<div class="d-flex justify-content-center py-4">
95+
<div class="spinner-border text-primary" role="status">
96+
<span class="visually-hidden">Loading...</span>
97+
</div>
98+
</div>
99+
<% end %>
100+
</div>
101+
</div>
102+
</div>
103+
104+
<!-- Privacy Tab -->
105+
<div class="nav-tab-pane tab-pane fade" id="privacy" role="tabpanel" aria-labelledby="privacy-tab">
106+
<div class="card border-0 shadow-sm">
107+
<div class="card-body">
108+
<h3 class="card-title h5">
109+
<i class="fa-solid fa-shield-alt me-2" aria-hidden="true"></i>
110+
<%= t('.privacy.title') %>
111+
</h3>
112+
<p class="text-muted mb-4"><%= t('.privacy.description') %></p>
113+
114+
<!-- Privacy settings content will go here -->
115+
<div class="alert alert-info" role="status">
116+
<i class="fa-solid fa-info-circle me-2" aria-hidden="true"></i>
117+
<%= t('.privacy.coming_soon') %>
118+
</div>
119+
</div>
120+
</div>
121+
</div>
122+
</div>
123+
</div>
124+
</div>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<%= turbo_frame_tag "account-settings" do %>
2+
<div class="container mt-0">
3+
<div class="row">
4+
<div class="col-12">
5+
<%= 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| %>
6+
<%= render "devise/shared/error_messages", resource: resource %>
7+
<div id="form_errors"></div>
8+
<!-- Email Field -->
9+
<div class="mb-3">
10+
<%= f.label :email, class: 'form-label' %>
11+
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control' %>
12+
</div>
13+
14+
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
15+
<div class="alert alert-info">
16+
<%= t('devise.registrations.edit.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email) %>
17+
</div>
18+
<% end %>
19+
20+
<!-- Password Field -->
21+
<div class="mb-3">
22+
<%= f.label :password, class: 'form-label' %>
23+
<i>(<%= t('devise.registrations.edit.leave_blank_if_you_don_t_want_to_change_it') %>)</i>
24+
<%= f.password_field :password, autocomplete: "new-password", class: 'form-control' %>
25+
<% if @minimum_password_length %>
26+
<small class="form-text text-muted">
27+
<%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %>
28+
</small>
29+
<% end %>
30+
</div>
31+
32+
<!-- Password Confirmation Field -->
33+
<div class="mb-3">
34+
<%= f.label :password_confirmation, class: 'form-label' %>
35+
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'form-control' %>
36+
</div>
37+
38+
<!-- Current Password Field -->
39+
<div class="mb-3">
40+
<%= required_label f, :current_password, class: 'form-label' %>
41+
<i>(<%= t('devise.registrations.edit.we_need_your_current_password_to_confirm_your_changes') %>)</i>
42+
<%= f.password_field :current_password, autocomplete: "current-password", class: 'form-control', required: true %>
43+
</div>
44+
45+
<!-- Update Button -->
46+
<div class="text-center mb-4">
47+
<%= f.submit t('devise.registrations.edit.update'), class: 'btn btn-primary' %>
48+
</div>
49+
<% end %>
50+
51+
<% if params[:cancel] %>
52+
<h3><%= t('devise.registrations.edit.cancel_my_account') %></h3>
53+
<div class="mb-3">
54+
<%= 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' %>
55+
</div>
56+
<% end %>
57+
</div>
58+
</div>
59+
</div>
60+
<% end %>

0 commit comments

Comments
 (0)