spree_cm_commissioner 2.12.0 → 2.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/spree/admin/users_controller_decorator.rb +9 -0
- data/app/controllers/spree/api/v2/storefront/queue_draft_orders_controller.rb +5 -3
- data/app/jobs/spree_cm_commissioner/queue_draft_orders/process_job.rb +7 -3
- data/app/models/concerns/spree_cm_commissioner/user_identity.rb +21 -2
- data/app/models/spree_cm_commissioner/user_decorator.rb +1 -1
- data/app/overrides/spree/admin/users/_form/user_add_on.html.erb.deface +2 -2
- data/app/overrides/spree/admin/users/index/body.html.erb.deface +3 -0
- data/app/overrides/spree/admin/users/index/headers.html.erb.deface +3 -0
- data/app/overrides/spree/admin/users/index/ransack.html.erb.deface +6 -0
- data/app/services/spree_cm_commissioner/users/dedupe_logins.rb +48 -0
- data/app/services/spree_cm_commissioner/users/qr_data/verify.rb +1 -1
- data/app/services/spree_cm_commissioner/users/username/generate.rb +1 -1
- data/app/views/spree/admin/user_security/show.html.erb +46 -5
- data/db/migrate/20260828080349_add_normalized_login_to_spree_users.rb +39 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +4 -3
- data/app/overrides/spree/admin/users/edit/account_status.html.erb.deface +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91f4f80720e207b672673f48cdfbf75d0efeaba681a61a7177c537c8741d5ccf
|
|
4
|
+
data.tar.gz: af1ca6770a66095ff54bf84dcb2840fa0a5c6578de079e8679cd28307182bc20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86639ec7ba1a728feaa59ed52ddf25e02687988611ff687ede1d0a8af1eb86590a26f95585a5f793ae549193cc2caa6aadee71fe6f0d5b51cdaa58b26437aebe
|
|
7
|
+
data.tar.gz: abae4f7a29cbe9f9963b682eb68c27f1dc77320a4495832da75d3ced6db2f8438a0c78b017362d49b21bdb800e1f78762eb03ce3122ce0834062a118ec6510f3
|
data/Gemfile.lock
CHANGED
|
@@ -3,6 +3,7 @@ module Spree
|
|
|
3
3
|
module UsersControllerDecorator
|
|
4
4
|
def self.prepended(base)
|
|
5
5
|
base.before_action :build_profile, only: %i[create update]
|
|
6
|
+
base.before_action :downcase_normalized_login_search_param, only: :index
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
# override
|
|
@@ -81,6 +82,14 @@ module Spree
|
|
|
81
82
|
|
|
82
83
|
@user.build_profile(attachment: permitted_resource_params.delete(:profile))
|
|
83
84
|
end
|
|
85
|
+
|
|
86
|
+
# normalized_login is stored lowercase, so a mixed-case search term would never
|
|
87
|
+
# match under a plain LIKE/ILIKE — downcase it before Ransack builds the query.
|
|
88
|
+
def downcase_normalized_login_search_param
|
|
89
|
+
return unless params[:q]&.key?(:normalized_login_cont)
|
|
90
|
+
|
|
91
|
+
params[:q][:normalized_login_cont] = params[:q][:normalized_login_cont].to_s.downcase
|
|
92
|
+
end
|
|
84
93
|
end
|
|
85
94
|
end
|
|
86
95
|
end
|
|
@@ -3,8 +3,8 @@ module Spree
|
|
|
3
3
|
module V2
|
|
4
4
|
module Storefront
|
|
5
5
|
class QueueDraftOrdersController < ::Spree::Api::V2::ResourceController
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Sets Current.load_test_bypass from the X-Request-Verify secret. The hold is acquired later
|
|
7
|
+
# in ProcessJob, so `create` passes this flag through options for the job to restore there.
|
|
8
8
|
include SpreeCmCommissioner::LoadTestBypassable
|
|
9
9
|
|
|
10
10
|
# Accepts a draft order request without building it: writes a queued Firestore status
|
|
@@ -27,7 +27,9 @@ module Spree
|
|
|
27
27
|
options: {
|
|
28
28
|
device_id: device_id,
|
|
29
29
|
last_ip_address: request.remote_ip,
|
|
30
|
-
private_sale_token: params[:st]
|
|
30
|
+
private_sale_token: params[:st],
|
|
31
|
+
# Carried into the job so it can bypass hold limits for a load test (see ProcessJob).
|
|
32
|
+
load_test_bypass: SpreeCmCommissioner::Current.load_test_bypass
|
|
31
33
|
}
|
|
32
34
|
},
|
|
33
35
|
locale: I18n.locale.to_s
|
|
@@ -13,9 +13,13 @@ module SpreeCmCommissioner
|
|
|
13
13
|
|
|
14
14
|
def perform(document_path, create_type, creation_params, locale = nil)
|
|
15
15
|
I18n.with_locale(locale.presence || I18n.default_locale) do
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
16
|
+
# Current doesn't cross into the job, so restore the load-test-bypass flag here — otherwise
|
|
17
|
+
# ValidateLimits would reject a trusted load test.
|
|
18
|
+
SpreeCmCommissioner::Current.set(load_test_bypass: creation_params.dig(:options, :load_test_bypass) || false) do
|
|
19
|
+
SpreeCmCommissioner::QueueDraftOrders::Process.call(
|
|
20
|
+
document_path: document_path, create_type: create_type, creation_params: creation_params
|
|
21
|
+
)
|
|
22
|
+
end
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
|
@@ -21,8 +21,10 @@ module SpreeCmCommissioner
|
|
|
21
21
|
validates :phone_number, uniqueness: { scope: :tenant_id, allow_blank: true }
|
|
22
22
|
validates :phone_number, format: { with: /^\d{8,12}$/, :multiline => true, allow_blank: true }
|
|
23
23
|
|
|
24
|
+
before_validation :normalize_login
|
|
25
|
+
|
|
24
26
|
validates :login, presence: true, if: -> (u) { u.require_login_identity_all_blank_for(:login) && !u.registering_via_social_network }
|
|
25
|
-
|
|
27
|
+
validate :validate_normalized_login_uniqueness
|
|
26
28
|
|
|
27
29
|
_validators.reject! { |key, _| key == :email }
|
|
28
30
|
_validate_callbacks.each { |c| c.filter.attributes.delete(:email) if c.filter.respond_to?(:attributes) }
|
|
@@ -46,7 +48,7 @@ module SpreeCmCommissioner
|
|
|
46
48
|
elsif login =~ URI::MailTo::EMAIL_REGEXP
|
|
47
49
|
scope.find_by(email: login)
|
|
48
50
|
else
|
|
49
|
-
scope.find_by(
|
|
51
|
+
scope.find_by(normalized_login: login)
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
end
|
|
@@ -58,6 +60,23 @@ module SpreeCmCommissioner
|
|
|
58
60
|
|
|
59
61
|
protected
|
|
60
62
|
|
|
63
|
+
# Keeps a plain, indexable lowercase copy of `login` so uniqueness and lookups can
|
|
64
|
+
# use a straight equality match instead of LOWER(login) everywhere, without ever
|
|
65
|
+
# rewriting the user's own `login` value (shown to them as a real handle elsewhere).
|
|
66
|
+
def normalize_login
|
|
67
|
+
self.normalized_login = login.present? ? login.downcase : nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Attaches the error to :login (the field shown to and edited by the user), not
|
|
71
|
+
# :normalized_login, so existing forms/specs keyed on `errors[:login]` keep working.
|
|
72
|
+
def validate_normalized_login_uniqueness
|
|
73
|
+
return if normalized_login.blank?
|
|
74
|
+
|
|
75
|
+
scope = self.class.where(tenant_id: tenant_id, normalized_login: normalized_login)
|
|
76
|
+
scope = scope.where.not(id: id) if persisted?
|
|
77
|
+
errors.add(:login, :taken) if scope.exists?
|
|
78
|
+
end
|
|
79
|
+
|
|
61
80
|
def require_login_identity_all_blank_for(field)
|
|
62
81
|
required_fields = %i[phone_number email user_identity_providers login].reject { |item| item == field }
|
|
63
82
|
required_fields.each do |required_field|
|
|
@@ -94,7 +94,7 @@ module SpreeCmCommissioner
|
|
|
94
94
|
base.before_save :update_otp_enabled
|
|
95
95
|
|
|
96
96
|
base.attr_accessor :assigned_roles
|
|
97
|
-
base.whitelisted_ransackable_attributes = %w[email first_name last_name gender phone_number blocked_at]
|
|
97
|
+
base.whitelisted_ransackable_attributes = %w[email first_name last_name gender phone_number blocked_at normalized_login]
|
|
98
98
|
base.devise :two_factor_authenticatable
|
|
99
99
|
def base.end_users
|
|
100
100
|
joins('LEFT JOIN spree_vendor_users ON spree_users.id = spree_vendor_users.user_id').where(spree_vendor_users: { user_id: nil })
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
service_types = SpreeCmCommissioner::ServiceType.translated_service_types.map { |key, label| [label, key.to_s] }
|
|
5
5
|
%>
|
|
6
6
|
|
|
7
|
-
<div data-hook="admin_user_form_user_section" class="p-3 mt-4 rounded
|
|
7
|
+
<div data-hook="admin_user_form_user_section" class="p-3 mt-4 border rounded form-group bg-light">
|
|
8
8
|
<strong class="mb-3 d-block"><%= Spree.t(:add_on) %></strong>
|
|
9
9
|
|
|
10
10
|
<%= f.field_container :primary_service_type do %>
|
|
11
11
|
<%= f.label :primary_service_type, Spree.t(:primary_service_type) %>
|
|
12
12
|
<%= f.select :primary_service_type,
|
|
13
|
-
options_for_select(service_types, f.object.primary_service_type),
|
|
13
|
+
options_for_select(service_types, f.object.primary_service_type || service_types.first&.last),
|
|
14
14
|
{ include_blank: Spree.t(:select_service_type) },
|
|
15
15
|
class: 'form-control select2-clear' %>
|
|
16
16
|
<%= f.error_message_on :primary_service_type %>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
<th>
|
|
5
5
|
<%= sort_link @search, :email, Spree.t(:email), {}, {title: 'users_email_title'} %>
|
|
6
6
|
</th>
|
|
7
|
+
<th>
|
|
8
|
+
<%= sort_link @search, :login, Spree.t(:login), {}, {title: 'login_title'} %>
|
|
9
|
+
</th>
|
|
7
10
|
<th>
|
|
8
11
|
<%= sort_link @search, :first_name, Spree.t(:first_name), {}, {title: 'first_name_title'} %>
|
|
9
12
|
</th>
|
|
@@ -39,6 +39,12 @@
|
|
|
39
39
|
<%= f.telephone_field :phone_number_cont, class: 'form-control js-filterable', autofocus: true, placeholder: 'xxx-xxx-xxxx'%>
|
|
40
40
|
</div>
|
|
41
41
|
</div>
|
|
42
|
+
<div class="col-12 col-lg-6">
|
|
43
|
+
<div class="form-group">
|
|
44
|
+
<%= f.label :normalized_login_cont, Spree.t(:login) %>
|
|
45
|
+
<%= f.text_field :normalized_login_cont, class: 'form-control js-filterable' %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
42
48
|
<div class="col-12 col-lg-6">
|
|
43
49
|
<div class="form-group">
|
|
44
50
|
<%# blocked_at_null: false = blocked, true = active. Ransack drops the blank option, so an
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Users
|
|
3
|
+
class DedupeLogins
|
|
4
|
+
prepend Spree::ServiceModule::Base
|
|
5
|
+
|
|
6
|
+
def call
|
|
7
|
+
renamed_count = duplicate_groups.sum { |tenant_id, normalized_login| rename_duplicates(tenant_id, normalized_login) }
|
|
8
|
+
success(renamed_count: renamed_count)
|
|
9
|
+
rescue StandardError => e
|
|
10
|
+
failure(nil, e.message)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# login uniqueness is scoped to tenant_id, so duplicates must be grouped the same way
|
|
16
|
+
# or users in different tenants would be falsely flagged as colliding.
|
|
17
|
+
def duplicate_groups
|
|
18
|
+
Spree::User.where.not(normalized_login: [nil, ''])
|
|
19
|
+
.group(:tenant_id, :normalized_login)
|
|
20
|
+
.having('count(*) > 1')
|
|
21
|
+
.pluck(:tenant_id, :normalized_login)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def rename_duplicates(tenant_id, normalized_login)
|
|
25
|
+
# keep the oldest account's login untouched; rename the rest
|
|
26
|
+
duplicates = Spree::User.where(tenant_id: tenant_id, normalized_login: normalized_login)
|
|
27
|
+
.order(:created_at)
|
|
28
|
+
.offset(1)
|
|
29
|
+
.to_a
|
|
30
|
+
|
|
31
|
+
duplicates.each { |user| rename_user(user, tenant_id) }
|
|
32
|
+
duplicates.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rename_user(user, tenant_id)
|
|
36
|
+
old_login = user.login
|
|
37
|
+
new_login = SpreeCmCommissioner::Users::Username::Generate.call(user: user)
|
|
38
|
+
# update_column skips callbacks, so normalized_login must be set explicitly here too.
|
|
39
|
+
user.update_columns(login: new_login, normalized_login: new_login.downcase) # rubocop:disable Rails/SkipsModelValidations
|
|
40
|
+
|
|
41
|
+
Rails.logger.info do
|
|
42
|
+
"[SpreeCmCommissioner::Users::DedupeLogins] user_id=#{user.id} tenant_id=#{tenant_id.inspect} " \
|
|
43
|
+
"login=#{old_login.inspect} -> #{new_login.inspect}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -18,7 +18,7 @@ module SpreeCmCommissioner
|
|
|
18
18
|
login = payload['login']
|
|
19
19
|
|
|
20
20
|
# we don't reveal whether user exists or not in error messages for security reasons.
|
|
21
|
-
user = Spree::User.find_by(
|
|
21
|
+
user = Spree::User.find_by(normalized_login: login.to_s.downcase)
|
|
22
22
|
raise JWT::DecodeError, 'QR code is invalid' if user.nil?
|
|
23
23
|
|
|
24
24
|
JWT.decode(qr_data, user.secure_token, true, { algorithm: 'HS256' })
|
|
@@ -27,7 +27,7 @@ module SpreeCmCommissioner
|
|
|
27
27
|
def call
|
|
28
28
|
@max_attempts.times do
|
|
29
29
|
username = generate_clean_username
|
|
30
|
-
return username unless Spree::User.exists?(
|
|
30
|
+
return username unless Spree::User.exists?(normalized_login: username.downcase)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Last resort: use extended number range
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<%= render partial: 'spree/admin/users/tabs', locals: { current: :security } %>
|
|
2
2
|
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="shadow-sm card">
|
|
4
4
|
<div class="card-header bg-light">
|
|
5
|
-
<h5 class="card-title
|
|
5
|
+
<h5 class="mb-0 card-title h6">
|
|
6
6
|
<%= Spree.t(:two_factor_authentication) %>
|
|
7
7
|
</h5>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<div class="card-body
|
|
10
|
+
<div class="text-center card-body">
|
|
11
11
|
<% if @user.otp_secret.present? && @user.otp_required_for_login == true %>
|
|
12
|
-
<p class="text-muted
|
|
12
|
+
<p class="mb-3 text-muted">
|
|
13
13
|
Two-factor authentication is currently active for this user.
|
|
14
14
|
</p>
|
|
15
15
|
<%= button_to disable_authenticator_admin_user_security_path, method: :delete, class: "btn btn-danger btn-md",
|
|
@@ -17,9 +17,50 @@
|
|
|
17
17
|
Disable 2FA
|
|
18
18
|
<% end %>
|
|
19
19
|
<% else %>
|
|
20
|
-
<p class="text-muted
|
|
20
|
+
<p class="mb-0 text-muted">
|
|
21
21
|
Two-factor authentication is <strong>not enabled</strong> for this user.
|
|
22
22
|
</p>
|
|
23
23
|
<% end %>
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
26
|
+
|
|
27
|
+
<div data-hook="admin_user_account_status" class="mt-3 shadow-sm card">
|
|
28
|
+
<div class="card-header bg-light">
|
|
29
|
+
<h5 class="mb-0 card-title h6">
|
|
30
|
+
<%= I18n.t('spree_user.account_status') %>
|
|
31
|
+
</h5>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="card-body">
|
|
35
|
+
<% if @user.blocked? %>
|
|
36
|
+
<p>
|
|
37
|
+
<span class="badge badge-danger text-uppercase"><%= I18n.t('spree_user.blocked') %></span>
|
|
38
|
+
<span class="ml-2 text-muted"><%= I18n.t('spree_user.blocked_at', time: l(@user.blocked_at, format: :long)) %></span>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<% if @user.blocked_reason.present? %>
|
|
42
|
+
<p class="text-muted"><%= @user.blocked_reason %></p>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<%= form_tag spree.unblock_admin_user_path(@user), method: :put do %>
|
|
46
|
+
<%= button_tag I18n.t('spree_user.unblock_user'),
|
|
47
|
+
type: 'submit',
|
|
48
|
+
class: 'btn btn-primary',
|
|
49
|
+
data: { confirm: I18n.t('spree_user.unblock_confirmation') } %>
|
|
50
|
+
<% end %>
|
|
51
|
+
<% else %>
|
|
52
|
+
<%= form_tag spree.block_admin_user_path(@user), method: :put do %>
|
|
53
|
+
<div class="form-group">
|
|
54
|
+
<%= label_tag :blocked_reason, I18n.t('spree_user.blocked_reason') %>
|
|
55
|
+
<%= text_field_tag :blocked_reason, nil, class: 'form-control' %>
|
|
56
|
+
<small class="form-text text-muted"><%= I18n.t('spree_user.blocked_reason_hint') %></small>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<%= button_tag I18n.t('spree_user.block_user'),
|
|
60
|
+
type: 'submit',
|
|
61
|
+
class: 'btn btn-danger',
|
|
62
|
+
data: { confirm: I18n.t('spree_user.block_confirmation') } %>
|
|
63
|
+
<% end %>
|
|
64
|
+
<% end %>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class AddNormalizedLoginToSpreeUsers < ActiveRecord::Migration[7.0]
|
|
2
|
+
def up
|
|
3
|
+
add_column :spree_users, :normalized_login, :string unless column_exists?(:spree_users, :normalized_login)
|
|
4
|
+
|
|
5
|
+
execute <<~SQL.squish
|
|
6
|
+
UPDATE spree_users SET normalized_login = LOWER(login)
|
|
7
|
+
WHERE login IS NOT NULL AND login != ''
|
|
8
|
+
SQL
|
|
9
|
+
|
|
10
|
+
# Case-insensitive login uniqueness has only ever been enforced by the Rails
|
|
11
|
+
# validation, not the DB, so case-variant duplicates already exist. Clean them
|
|
12
|
+
# up first or the unique index below fails to create.
|
|
13
|
+
result = SpreeCmCommissioner::Users::DedupeLogins.call
|
|
14
|
+
raise "Failed to dedupe user logins before indexing: #{result.error}" if result.failure?
|
|
15
|
+
|
|
16
|
+
unless index_exists?(:spree_users, %i[tenant_id normalized_login], name: 'index_spree_users_on_tenant_id_and_normalized_login')
|
|
17
|
+
add_index :spree_users, %i[tenant_id normalized_login],
|
|
18
|
+
unique: true,
|
|
19
|
+
where: 'tenant_id IS NOT NULL AND normalized_login IS NOT NULL',
|
|
20
|
+
name: 'index_spree_users_on_tenant_id_and_normalized_login'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# tenant_id IS NULL rows aren't covered by the composite index above (NULL never
|
|
24
|
+
# equals NULL in a unique index), but the validation's `scope: :tenant_id` treats
|
|
25
|
+
# all null-tenant users as one group, so they need their own partial index to match.
|
|
26
|
+
return if index_exists?(:spree_users, :normalized_login, name: 'index_spree_users_on_normalized_login_when_tenant_id_null')
|
|
27
|
+
|
|
28
|
+
add_index :spree_users, :normalized_login,
|
|
29
|
+
unique: true,
|
|
30
|
+
where: 'tenant_id IS NULL AND normalized_login IS NOT NULL',
|
|
31
|
+
name: 'index_spree_users_on_normalized_login_when_tenant_id_null'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def down
|
|
35
|
+
remove_index :spree_users, name: 'index_spree_users_on_tenant_id_and_normalized_login', if_exists: true
|
|
36
|
+
remove_index :spree_users, name: 'index_spree_users_on_normalized_login_when_tenant_id_null', if_exists: true
|
|
37
|
+
remove_column :spree_users, :normalized_login if column_exists?(:spree_users, :normalized_login)
|
|
38
|
+
end
|
|
39
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_cm_commissioner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.12.
|
|
4
|
+
version: 2.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|
|
@@ -1938,7 +1938,6 @@ files:
|
|
|
1938
1938
|
- app/overrides/spree/admin/users/_form/roles_fields.html.erb.deface
|
|
1939
1939
|
- app/overrides/spree/admin/users/_form/user_add_on.html.erb.deface
|
|
1940
1940
|
- app/overrides/spree/admin/users/_tabs/tabs.html.erb.deface
|
|
1941
|
-
- app/overrides/spree/admin/users/edit/account_status.html.erb.deface
|
|
1942
1941
|
- app/overrides/spree/admin/users/index/body.html.erb.deface
|
|
1943
1942
|
- app/overrides/spree/admin/users/index/headers.html.erb.deface
|
|
1944
1943
|
- app/overrides/spree/admin/users/index/ransack.html.erb.deface
|
|
@@ -2424,6 +2423,7 @@ files:
|
|
|
2424
2423
|
- app/services/spree_cm_commissioner/user_counters_service.rb
|
|
2425
2424
|
- app/services/spree_cm_commissioner/user_roles_assigner.rb
|
|
2426
2425
|
- app/services/spree_cm_commissioner/users/block.rb
|
|
2426
|
+
- app/services/spree_cm_commissioner/users/dedupe_logins.rb
|
|
2427
2427
|
- app/services/spree_cm_commissioner/users/qr_data/extract_login.rb
|
|
2428
2428
|
- app/services/spree_cm_commissioner/users/qr_data/generate.rb
|
|
2429
2429
|
- app/services/spree_cm_commissioner/users/qr_data/invalidate.rb
|
|
@@ -3547,6 +3547,7 @@ files:
|
|
|
3547
3547
|
- db/migrate/20260820100000_create_cm_private_sales.rb
|
|
3548
3548
|
- db/migrate/20260824120000_add_user_id_to_cm_waiting_room_sessions.rb
|
|
3549
3549
|
- db/migrate/20260825092416_add_live_stock_window_to_spree_taxons.rb
|
|
3550
|
+
- db/migrate/20260828080349_add_normalized_login_to_spree_users.rb
|
|
3550
3551
|
- docker-compose.yml
|
|
3551
3552
|
- docs/api/scoped-access-token-endpoints.md
|
|
3552
3553
|
- docs/option_types/attr_types.md
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
<!-- insert_after "[data-hook='admin_user_edit_general_settings']" -->
|
|
2
|
-
|
|
3
|
-
<div data-hook="admin_user_account_status" class="card mb-3">
|
|
4
|
-
<div class="card-header">
|
|
5
|
-
<h5 class="card-title mb-0 h6">
|
|
6
|
-
<%= I18n.t('spree_user.account_status') %>
|
|
7
|
-
</h5>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="card-body">
|
|
11
|
-
<% if @user.blocked? %>
|
|
12
|
-
<p>
|
|
13
|
-
<span class="badge badge-danger text-uppercase"><%= I18n.t('spree_user.blocked') %></span>
|
|
14
|
-
<span class="text-muted ml-2"><%= I18n.t('spree_user.blocked_at', time: l(@user.blocked_at, format: :long)) %></span>
|
|
15
|
-
</p>
|
|
16
|
-
|
|
17
|
-
<% if @user.blocked_reason.present? %>
|
|
18
|
-
<p class="text-muted"><%= @user.blocked_reason %></p>
|
|
19
|
-
<% end %>
|
|
20
|
-
|
|
21
|
-
<%= form_tag spree.unblock_admin_user_path(@user), method: :put do %>
|
|
22
|
-
<%= button_tag I18n.t('spree_user.unblock_user'),
|
|
23
|
-
type: 'submit',
|
|
24
|
-
class: 'btn btn-primary',
|
|
25
|
-
data: { confirm: I18n.t('spree_user.unblock_confirmation') } %>
|
|
26
|
-
<% end %>
|
|
27
|
-
<% else %>
|
|
28
|
-
<%= form_tag spree.block_admin_user_path(@user), method: :put do %>
|
|
29
|
-
<div class="form-group">
|
|
30
|
-
<%= label_tag :blocked_reason, I18n.t('spree_user.blocked_reason') %>
|
|
31
|
-
<%= text_field_tag :blocked_reason, nil, class: 'form-control' %>
|
|
32
|
-
<small class="form-text text-muted"><%= I18n.t('spree_user.blocked_reason_hint') %></small>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<%= button_tag I18n.t('spree_user.block_user'),
|
|
36
|
-
type: 'submit',
|
|
37
|
-
class: 'btn btn-danger',
|
|
38
|
-
data: { confirm: I18n.t('spree_user.block_confirmation') } %>
|
|
39
|
-
<% end %>
|
|
40
|
-
<% end %>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|