Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion app/admin/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
link_to I18n.t("active_admin.users.upload_from_csv"), action: "upload_csv"
end

action_item :confirm, only: :show do
unless user.confirmed?
link_to I18n.t("active_admin.users.confirm_user"),
confirm_admin_user_path(user),
method: :put
end
end

collection_action :upload_csv do
render "admin/csv/upload_csv"
end
Expand All @@ -16,6 +24,15 @@
redirect_to action: :index
end

member_action :confirm, method: :put do
resource.skip_confirmation!
if resource.save
redirect_to admin_user_path(resource), notice: I18n.t("active_admin.users.confirmed_notice")
else
redirect_to admin_user_path(resource), alert: resource.errors.full_messages.to_sentence
end
end
Comment on lines +27 to +34
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New admin behaviors are introduced here (manual confirmation endpoint and the ability to set/retain passwords via the ActiveAdmin form/controller overrides) but there are no specs covering them. Adding request/controller specs around PUT /admin/users/:id/confirm, create with/without confirm_immediately, and update with blank vs non-blank password would help prevent regressions (especially around Devise confirmable).

Copilot uses AI. Check for mistakes.

scope :all
scope :without_memberships

Expand All @@ -32,6 +49,7 @@
column :posts do |u|
u.posts.count
end
column :confirmed_at
column :created_at
actions
end
Expand All @@ -53,6 +71,9 @@
f.input :postcode
f.input :gender, as: :select, collection: User::GENDERS
f.input :locale, as: :select, collection: I18n.available_locales
f.input :password, required: false, input_html: { autocomplete: "new-password" }
f.input :password_confirmation, required: false, input_html: { autocomplete: "new-password" }
f.input :confirm_immediately, as: :boolean if f.object.new_record?
end
f.inputs "Memberships" do
f.has_many :members, allow_destroy: true do |m|
Expand Down Expand Up @@ -91,6 +112,22 @@
end
end

permit_params :username, :email, :phone, :postcode, :gender, :locale,
permit_params :username, :email, :phone, :postcode, :gender, :locale, :confirm_immediately,
:password, :password_confirmation,
members_attributes: [:id, :organization_id, :active, :manager, :_destroy]

controller do
def create_resource(obj)
obj.skip_confirmation! if obj.confirm_immediately
Comment thread
rewritten marked this conversation as resolved.
super
end

def update_resource(obj, attributes)
if attributes.first[:password].blank?
attributes.first.delete(:password)
attributes.first.delete(:password_confirmation)
end
super
end
end
end
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class User < ApplicationRecord

attr_accessor :empty_email
attr_accessor :from_signup
attribute :confirm_immediately, :boolean

has_one_attached :avatar

Expand Down
3 changes: 3 additions & 0 deletions config/locales/ca.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ ca:
active_admin:
users:
organization: Banc de temps
confirm_user: Confirmar usuari
confirmed_notice: Usuari confirmat correctament
upload_csv: Fitxer
upload_from_csv: Importar CSV
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ ca:
registration_date: Data d'alta
registration_number: Codi d'usuari
superadmin: SuperAdministrador del Sistema
confirm_immediately: Confirmar immediatament
unconfirmed_email: Correu sense confirmar
updated_at: Actualitzat
username: Nom
Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ en:
active_admin:
users:
organization: Time bank
confirm_user: Confirm user
confirmed_notice: User confirmed successfully
upload_csv: File
upload_from_csv: Upload CSV
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ en:
registration_date: Registration date
registration_number: User code
superadmin: System Administrator
confirm_immediately: Confirm immediately
unconfirmed_email: Unconfirmed Email
updated_at: Updated
username: Name
Expand Down
3 changes: 3 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ es:
active_admin:
users:
organization: Banco de tiempo
confirm_user: Confirmar usuario
confirmed_notice: Usuario confirmado correctamente
upload_csv: Fichero
upload_from_csv: Importar CSV
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ es:
registration_date: Fecha de alta
registration_number: Código de usuario
superadmin: Administrador de sistema
confirm_immediately: Confirmar inmediatamente
unconfirmed_email: Correo sin confirmar
updated_at: Actualizado
username: Nombre
Expand Down
3 changes: 3 additions & 0 deletions config/locales/eu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ eu:
active_admin:
users:
organization: Denbora Bankua
confirm_user: Erabiltzailea baieztatu
confirmed_notice: Erabiltzailea berretsita
upload_csv: Fitxategia
upload_from_csv: Igo CSVa
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ eu:
registration_date: Erregistratze-da
registration_number: Erabiltzaile kodea
superadmin: Sistema administratzailea
confirm_immediately: Berehala baieztatu
unconfirmed_email: Baieztatu gabeko eposta
updated_at: Eguneratua
username: Izena
Expand Down
3 changes: 3 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ fr:
active_admin:
users:
organization: Banque de temps
confirm_user: "Confirmer l'utilisateur"
confirmed_notice: "Utilisateur confirmé avec succès"
upload_csv: Fichier
upload_from_csv: Uploader un CSV
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ fr:
registration_date: Date d'inscription
registration_number: Numéro d'inscription
superadmin: Administrateur système
confirm_immediately: Confirmer immédiatement
unconfirmed_email: Adresse email non-confirmée
updated_at: Mis·e à jour le
username: Nom
Expand Down
3 changes: 3 additions & 0 deletions config/locales/gl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ gl:
active_admin:
users:
organization: Banco de tempo
confirm_user: Confirmar usuario
confirmed_notice: Usuario confirmado correctamente
upload_csv: Ficheiro
upload_from_csv: Importar CSV
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ gl:
registration_date: Data de rexistro
registration_number: Código de persoa usuaria
superadmin: Persoa dministradora do sistema
confirm_immediately: Confirmar inmediatamente
unconfirmed_email: Correo electrónico non confirmado
updated_at: Actualizado
username: Nome
Expand Down
3 changes: 3 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ ja:
active_admin:
users:
organization: タイムバンク
confirm_user: ユーザーを確認
confirmed_notice: ユーザーが確認されました
upload_csv: ファイル
upload_from_csv: CSVをインポート
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ ja:
registration_date: 登録日
registration_number: ユーザー番号
superadmin: システム管理者
confirm_immediately: すぐに確認する
unconfirmed_email: 未確認メールアドレス
updated_at: 更新
username: 名前
Expand Down
3 changes: 3 additions & 0 deletions config/locales/pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pt-BR:
active_admin:
users:
organization: Banco de Tempo
confirm_user: Confirmar usuário
confirmed_notice: Usuário confirmado com sucesso
upload_csv: Arquivo
upload_from_csv: Importar CSV
activerecord:
Expand Down Expand Up @@ -70,6 +72,7 @@ pt-BR:
registration_date: Data de ingresso
registration_number: Código do usuário
superadmin: Administrador do sistema
confirm_immediately: Confirmar imediatamente
unconfirmed_email: E-mail sem confirmação
updated_at: Atualizado
username: Nome
Expand Down
79 changes: 79 additions & 0 deletions spec/admin/users_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
RSpec.describe Admin::UsersController, type: :controller do
let(:organization) { Fabricate(:organization) }
let(:member) { Fabricate(:member, organization: organization) }
let(:user) { member.user }

before do
login(user)
allow(controller).to receive(:authenticate_superuser!).and_return(true)
end

describe "PUT #confirm" do
context "when the user is unconfirmed" do
let(:unconfirmed_user) { Fabricate(:user, confirmed_at: nil) }

it "confirms the user and redirects with notice" do
put :confirm, params: { id: unconfirmed_user.id }

expect(unconfirmed_user.reload.confirmed?).to be true
expect(response).to redirect_to(admin_user_path(unconfirmed_user))
expect(flash[:notice]).to eq(I18n.t("active_admin.users.confirmed_notice"))
end
end

context "when the user is already confirmed" do
it "re-confirms and redirects with notice" do
put :confirm, params: { id: user.id }

expect(response).to redirect_to(admin_user_path(user))
expect(flash[:notice]).to eq(I18n.t("active_admin.users.confirmed_notice"))
end
end
end

describe "POST #create" do
let(:valid_params) do
{ username: "newuser", email: "new@example.com", locale: "en" }
end

context "with confirm_immediately checked" do
it "creates a confirmed user" do
post :create, params: { user: valid_params.merge(confirm_immediately: "1") }

created_user = User.find_by(email: "new@example.com")
expect(created_user).to be_confirmed
end
end

context "without confirm_immediately checked" do
it "creates an unconfirmed user" do
post :create, params: { user: valid_params.merge(confirm_immediately: "0") }

created_user = User.find_by(email: "new@example.com")
expect(created_user).not_to be_confirmed
end
end
end

describe "PUT #update" do
context "when password is blank" do
it "does not change the existing password" do
original_encrypted = user.encrypted_password

put :update, params: { id: user.id, user: { password: "", password_confirmation: "" } }

expect(user.reload.encrypted_password).to eq(original_encrypted)
end
end

context "when password is provided" do
it "updates the password" do
original_encrypted = user.encrypted_password

put :update, params: { id: user.id, user: { password: "newpassword123", password_confirmation: "newpassword123" } }

expect(user.reload.encrypted_password).not_to eq(original_encrypted)
end
end
end
end
Loading