spree_admin 5.6.0 → 5.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f76704b60d9db2f4b7943ed0bd9ddcb0a6b694615be432c5f31ed7d35e3fdeb
4
- data.tar.gz: 5ae7ecba30efe7ee4bf14bb5b03412b5db801f8c29809f6f9486cb8dc41067a1
3
+ metadata.gz: a9eb63c476e1a653a5b5833cd6414ccf0087835b80936c35d2e840e01f86a24e
4
+ data.tar.gz: 9badde295df91ab7863783d1cd855007995a0b5e4edfa301b2a8b566c1c3c74c
5
5
  SHA512:
6
- metadata.gz: c627b84de913d422bed71b8d6e34819119a555b833bebfeff9df53709e50f16b6c855691263dc72803ecb7c3fb04106493407392029df20b7d0505fc7621d5e6
7
- data.tar.gz: c0882faf606c490b9390821bd256a5bce117c7060c18576c7df1197186c7af81abf590405c007786aa23cf16473f3ee031922641697359b3b5e824c77f56558a
6
+ metadata.gz: c915f772bccbafcc2671fab2eda16124a5c89e619191b0bdacd5523c7b528f3bca104820d5be4d4f8a36a26f5495a756bacab5fc29856553a625c26db561cae3
7
+ data.tar.gz: 7129a17bfb814448aa513ef048fd141d98cd93bf633351dcd086a7296af1f074f0e6a864ea7133e1b70f23cdc6ebee271de3dc317d6fac5c7cbefc68100cf0ea
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ module Admin
3
+ class UserAddressesController < ResourceController
4
+ use_table :addresses
5
+
6
+ before_action :redirect_to_full_page, only: :index, unless: :turbo_frame_request?
7
+
8
+ private
9
+
10
+ def model_class
11
+ Spree::Address
12
+ end
13
+
14
+ def edit_object_url(_object, _options = {})
15
+ nil
16
+ end
17
+
18
+ def parent
19
+ @parent ||= Spree.user_class.accessible_by(current_ability, :show).find_by_prefix_id!(params[:user_id])
20
+ @user = @parent
21
+ end
22
+
23
+ def scope
24
+ parent.addresses.accessible_by(current_ability, :index).includes(collection_includes)
25
+ end
26
+
27
+ def collection_includes
28
+ %i[country state]
29
+ end
30
+
31
+ def collection_url(_options = {})
32
+ spree.admin_users_path
33
+ end
34
+
35
+ def redirect_to_full_page
36
+ redirect_to spree.admin_user_path(parent)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -63,6 +63,7 @@ import FontPickerController from 'spree/admin/controllers/font_picker_controller
63
63
  import HighlightController from 'spree/admin/controllers/highlight_controller'
64
64
  import ImportFormController from 'spree/admin/controllers/import_form_controller'
65
65
  import MediaFormController from 'spree/admin/controllers/media_form_controller'
66
+ import MetafieldFormController from 'spree/admin/controllers/metafield_form_controller'
66
67
  import MoneyFieldController from 'spree/admin/controllers/money_field_controller'
67
68
  import MultiInputController from 'spree/admin/controllers/multi_input_controller'
68
69
  import MultiTomSelectController from 'spree/admin/controllers/multi_tom_select_controller'
@@ -135,6 +136,7 @@ application.register('font-picker', FontPickerController)
135
136
  application.register('highlight', HighlightController)
136
137
  application.register('import-form', ImportFormController)
137
138
  application.register('media-form', MediaFormController)
139
+ application.register('metafield-form', MetafieldFormController)
138
140
  application.register('money-field', MoneyFieldController)
139
141
  application.register('multi-input', MultiInputController)
140
142
  application.register('multi-tom-select', MultiTomSelectController)
@@ -0,0 +1,79 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ // Mirrors Spree::MetafieldDefinition searchable/sortable type capabilities.
4
+ const SEARCHABLE_TYPES = new Set([
5
+ 'Spree::Metafields::ShortText',
6
+ 'Spree::Metafields::LongText',
7
+ 'Spree::Metafields::Number',
8
+ ])
9
+
10
+ const SORTABLE_TYPES = new Set([
11
+ 'Spree::Metafields::ShortText',
12
+ 'Spree::Metafields::Number',
13
+ ])
14
+
15
+ export default class extends Controller {
16
+ static targets = ['metafieldType', 'searchable', 'sortable']
17
+
18
+ connect() {
19
+ this.restoreCheckbox(this.searchableTarget)
20
+ this.restoreCheckbox(this.sortableTarget)
21
+ this.syncCapabilities()
22
+ }
23
+
24
+ typeChanged(event) {
25
+ if (this.hasMetafieldTypeTarget && event?.target && event.target !== this.metafieldTypeTarget) {
26
+ return
27
+ }
28
+
29
+ this.syncCapabilities()
30
+ this.clearCapabilityErrors(this.searchableTarget)
31
+ this.clearCapabilityErrors(this.sortableTarget)
32
+ this.clearAlertErrors(['searchable', 'sortable'])
33
+ }
34
+
35
+ syncCapabilities() {
36
+ if (!this.hasMetafieldTypeTarget) return
37
+
38
+ const type = this.metafieldTypeTarget.value
39
+ this.applyCapability(this.searchableTarget, SEARCHABLE_TYPES.has(type))
40
+ this.applyCapability(this.sortableTarget, SORTABLE_TYPES.has(type))
41
+ }
42
+
43
+ applyCapability(input, supported) {
44
+ if (!input) return
45
+
46
+ input.disabled = !supported
47
+ if (!supported) input.checked = false
48
+ }
49
+
50
+ // Unwrap .field_with_errors so input~label sibling styles work again.
51
+ // Leaves .formError text in place until Type changes.
52
+ restoreCheckbox(input) {
53
+ if (!input) return
54
+
55
+ const checkbox = input.closest('.form-checkbox')
56
+ checkbox?.querySelectorAll(':scope > .field_with_errors').forEach((wrapper) => {
57
+ wrapper.replaceWith(...wrapper.childNodes)
58
+ })
59
+ }
60
+
61
+ clearCapabilityErrors(input) {
62
+ if (!input) return
63
+
64
+ this.restoreCheckbox(input)
65
+ input.closest('.form-group')?.querySelector('.formError')?.remove()
66
+ }
67
+
68
+ clearAlertErrors(fields) {
69
+ const form = this.element.closest('form') || this.element
70
+ const alert = form.querySelector('.alert-danger')
71
+ if (!alert) return
72
+
73
+ alert.querySelectorAll('li').forEach((li) => {
74
+ const text = li.textContent.toLowerCase()
75
+ if (fields.some((name) => text.includes(name))) li.remove()
76
+ })
77
+ if (!alert.querySelector('li')) alert.remove()
78
+ }
79
+ }
@@ -16,6 +16,19 @@
16
16
  </div>
17
17
 
18
18
  <%= f.spree_select :preferred_delimiter, options_for_select([',', ';', '|', '\t'], @import.preferred_delimiter) %>
19
+
20
+ <div class="flex flex-col items-start gap-2 mt-4">
21
+ <%# The schema is one CSV line, so it's inlined rather than fetched. %>
22
+ <%= link_to_with_icon 'download', Spree.t('admin.imports.download_template_csv'),
23
+ "data:text/csv;charset=utf-8,#{ERB::Util.url_encode(@import.template_csv)}",
24
+ download: @import.template_csv_filename, class: 'text-muted' %>
25
+
26
+ <%# The populated counterpart to the headers-only template above. %>
27
+ <% if (sample_csv_url = @import.sample_csv_url) %>
28
+ <%= link_to_with_icon 'download', Spree.t('admin.imports.download_example_csv'), sample_csv_url,
29
+ target: '_blank', rel: 'noopener', class: 'text-muted' %>
30
+ <% end %>
31
+ </div>
19
32
  </div>
20
33
 
21
34
  <div class="drawer-footer">
@@ -1,4 +1,6 @@
1
- <div class="card mb-6">
1
+ <div class="card mb-6"
2
+ data-controller="metafield-form"
3
+ data-action="change->metafield-form#typeChanged">
2
4
  <div class="card-body">
3
5
  <div class="form-group">
4
6
  <%= f.spree_text_field :name, required: true, autofocus: f.object.new_record? %>
@@ -19,7 +21,10 @@
19
21
  </div>
20
22
 
21
23
  <div class="form-group">
22
- <%= f.spree_select :metafield_type, metafield_definition_types, { include_blank: true, autocomplete: true, label: Spree.t(:type) }, { required: true } %>
24
+ <%= f.spree_select :metafield_type,
25
+ metafield_definition_types,
26
+ { include_blank: true, autocomplete: true, label: Spree.t(:type) },
27
+ { required: true, data: { metafield_form_target: 'metafieldType' } } %>
23
28
  </div>
24
29
  <% else %>
25
30
  <div class="alert alert-warning">
@@ -40,5 +45,17 @@
40
45
  <div class="form-group">
41
46
  <%= f.spree_select :display_on, display_on_options(Spree::MetafieldDefinition), { include_blank: true, autocomplete: true }, { required: true } %>
42
47
  </div>
48
+
49
+ <div class="form-group">
50
+ <%= f.spree_check_box :searchable,
51
+ help: Spree.t('admin.metafield_definitions.searchable_help'),
52
+ data: { metafield_form_target: 'searchable' } %>
53
+ </div>
54
+
55
+ <div class="form-group">
56
+ <%= f.spree_check_box :sortable,
57
+ help: Spree.t('admin.metafield_definitions.sortable_help'),
58
+ data: { metafield_form_target: 'sortable' } %>
59
+ </div>
43
60
  </div>
44
- </div>
61
+ </div>
@@ -0,0 +1,9 @@
1
+ <div><%= record.full_name %></div>
2
+ <div class="flex flex-col items-start gap-1 mt-1">
3
+ <% if record.is_default_billing? %>
4
+ <span class="badge badge-info"><%= Spree.t(:default_billing_address) %></span>
5
+ <% end %>
6
+ <% if record.is_default_shipping? %>
7
+ <span class="badge badge-info"><%= Spree.t(:default_shipping_address) %></span>
8
+ <% end %>
9
+ </div>
@@ -0,0 +1 @@
1
+ <%= render_table @collection, :addresses, frame_name: :addresses %>
@@ -26,22 +26,28 @@
26
26
  <a class="nav-link" id="pills-store-credits-tab" data-tabs-target="tab" data-action="click->tabs#select" role="tab" aria-controls="pills-store-credits" aria-selected="false"><%= Spree.t(:store_credits) %></a>
27
27
  </li>
28
28
  <% end %>
29
+
30
+ <% if can?(:read, Spree::Address) %>
31
+ <li class="nav-item" role="presentation">
32
+ <a class="nav-link" id="pills-addresses-tab" data-tabs-target="tab" data-action="click->tabs#select" role="tab" aria-controls="pills-addresses" aria-selected="false"><%= Spree.t(:addresses) %></a>
33
+ </li>
34
+ <% end %>
29
35
  </ul>
30
36
 
31
37
  <% if spree.respond_to?(:admin_user_visits_path) %>
32
- <div data-tabs-target="panel" id="pills-overview" role="tabpanel" aria-labelledby="pills-overview" class="pt-4 animate-fade-in">
38
+ <div data-tabs-target="panel" id="pills-overview" role="tabpanel" aria-labelledby="pills-overview-tab" class="pt-4 animate-fade-in">
33
39
  <%= turbo_frame_tag :user_visits, src: spree.admin_user_visits_path(@user), target: '_top' %>
34
40
  </div>
35
41
  <% end %>
36
42
 
37
43
  <% if can?(:read, Spree::Order) %>
38
- <div data-tabs-target="panel" id="pills-orders2" role="tabpanel" aria-labelledby="pills-orders2" class="animate-fade-in" <%= 'hidden' if spree.respond_to?(:admin_user_visits_path) %>>
44
+ <div data-tabs-target="panel" id="pills-orders2" role="tabpanel" aria-labelledby="pills-orders2-tab" class="animate-fade-in" <%= 'hidden' if spree.respond_to?(:admin_user_visits_path) %>>
39
45
  <%= turbo_frame_tag :orders, src: spree.admin_user_orders_path(@user), loading: :lazy, autoscroll: true, data: { autoscroll_block: :nearest, autoscroll_behavior: :smooth } do %>
40
46
  <%= render 'spree/admin/shared/spinner' %>
41
47
  <% end %>
42
48
  </div>
43
49
 
44
- <div data-tabs-target="panel" id="pills-checkouts" role="tabpanel" aria-labelledby="pills-checkouts" hidden class="animate-fade-in">
50
+ <div data-tabs-target="panel" id="pills-checkouts" role="tabpanel" aria-labelledby="pills-checkouts-tab" hidden class="animate-fade-in">
45
51
  <%= turbo_frame_tag :checkouts, src: spree.admin_user_checkouts_path(@user), loading: :lazy, autoscroll: true, data: { autoscroll_block: :nearest, autoscroll_behavior: :smooth } do %>
46
52
  <%= render 'spree/admin/shared/spinner' %>
47
53
  <% end %>
@@ -49,7 +55,7 @@
49
55
  <% end %>
50
56
 
51
57
  <% if spree.respond_to?(:admin_user_gift_cards_path) && can?(:read, Spree::GiftCard) %>
52
- <div data-tabs-target="panel" id="pills-gift-cards" role="tabpanel" aria-labelledby="pills-gift-cards" hidden class="animate-fade-in">
58
+ <div data-tabs-target="panel" id="pills-gift-cards" role="tabpanel" aria-labelledby="pills-gift-cards-tab" hidden class="animate-fade-in">
53
59
  <%= turbo_frame_tag :gift_cards, src: spree.admin_user_gift_cards_path(@user), loading: :lazy, autoscroll: true, data: { autoscroll_block: :nearest, autoscroll_behavior: :smooth } do %>
54
60
  <%= render 'spree/admin/shared/spinner' %>
55
61
  <% end %>
@@ -57,7 +63,7 @@
57
63
  <% end %>
58
64
 
59
65
  <% if can?(:read, Spree::StoreCredit) %>
60
- <div data-tabs-target="panel" id="pills-store-credits" role="tabpanel" aria-labelledby="pills-store-credits" hidden class="animate-fade-in">
66
+ <div data-tabs-target="panel" id="pills-store-credits" role="tabpanel" aria-labelledby="pills-store-credits-tab" hidden class="animate-fade-in">
61
67
  <div class="card-lg">
62
68
  <%= turbo_frame_tag :store_credits_list, src: spree.admin_user_store_credits_path(@user, frame_name: :store_credits_list), loading: :lazy, autoscroll: true, data: { autoscroll_block: :nearest, autoscroll_behavior: :smooth } do %>
63
69
  <%= render 'spree/admin/shared/spinner' %>
@@ -65,4 +71,12 @@
65
71
  </div>
66
72
  </div>
67
73
  <% end %>
74
+
75
+ <% if can?(:read, Spree::Address) %>
76
+ <div data-tabs-target="panel" id="pills-addresses" role="tabpanel" aria-labelledby="pills-addresses-tab" hidden class="animate-fade-in">
77
+ <%= turbo_frame_tag :addresses, src: spree.admin_user_addresses_path(@user), loading: :lazy, autoscroll: true, data: { autoscroll_block: :nearest, autoscroll_behavior: :smooth } do %>
78
+ <%= render 'spree/admin/shared/spinner' %>
79
+ <% end %>
80
+ </div>
81
+ <% end %>
68
82
  </div>
@@ -2173,4 +2173,67 @@ Rails.application.config.after_initialize do
2173
2173
  filterable: true,
2174
2174
  default: true,
2175
2175
  position: 20
2176
+
2177
+ # Register Addresses table (customer address book)
2178
+ Spree.admin.tables.register(:addresses, model_class: Spree::Address, search_param: :firstname_or_lastname_or_address1_or_address2_or_city_or_zipcode_cont, row_actions: false, new_resource: false)
2179
+
2180
+ Spree.admin.tables.addresses.add :name,
2181
+ label: :name,
2182
+ type: :custom,
2183
+ sortable: false,
2184
+ filterable: false,
2185
+ default: true,
2186
+ position: 10,
2187
+ partial: 'spree/admin/tables/columns/address_name'
2188
+
2189
+ Spree.admin.tables.addresses.add :address,
2190
+ label: :address,
2191
+ type: :string,
2192
+ sortable: false,
2193
+ filterable: false,
2194
+ default: true,
2195
+ position: 20,
2196
+ method: ->(address) { [address.address1, address.address2].reject(&:blank?).join(', ') }
2197
+
2198
+ Spree.admin.tables.addresses.add :city,
2199
+ label: :city,
2200
+ type: :string,
2201
+ sortable: true,
2202
+ filterable: true,
2203
+ default: true,
2204
+ position: 30
2205
+
2206
+ Spree.admin.tables.addresses.add :state,
2207
+ label: :state,
2208
+ type: :string,
2209
+ sortable: false,
2210
+ filterable: false,
2211
+ default: true,
2212
+ position: 40,
2213
+ method: ->(address) { address.state_text }
2214
+
2215
+ Spree.admin.tables.addresses.add :zipcode,
2216
+ label: :zipcode,
2217
+ type: :string,
2218
+ sortable: true,
2219
+ filterable: true,
2220
+ default: true,
2221
+ position: 50
2222
+
2223
+ Spree.admin.tables.addresses.add :country,
2224
+ label: :country,
2225
+ type: :string,
2226
+ sortable: false,
2227
+ filterable: false,
2228
+ default: true,
2229
+ position: 60,
2230
+ method: ->(address) { localized_country_name(address.country) if address.country }
2231
+
2232
+ Spree.admin.tables.addresses.add :phone,
2233
+ label: :phone,
2234
+ type: :string,
2235
+ sortable: false,
2236
+ filterable: true,
2237
+ default: true,
2238
+ position: 70
2176
2239
  end
@@ -218,6 +218,8 @@ en:
218
218
  usage: Usage
219
219
  imports:
220
220
  all_required_columns_mapped: Good job, all required columns are mapped!
221
+ download_example_csv: Download an example CSV file
222
+ download_template_csv: Download CSV template
221
223
  field_required: Field required
222
224
  import_done: All done!
223
225
  json: JSON
@@ -258,6 +260,8 @@ en:
258
260
  supported_locales_hint: Comma-separated list of additional locale codes (e.g. fr,de,es)
259
261
  metafield_definitions:
260
262
  edit_warning: You can't change the resource type or metafield type after the definition has been used.
263
+ searchable_help: Include this field's values in storefront product text search (short text, long text, and number only). After enabling, run rake spree:search:reindex if using Meilisearch so existing products are updated.
264
+ sortable_help: Allow storefront product listings to sort by this field (short text and number only). After enabling, run rake spree:search:reindex if using Meilisearch so existing products are updated.
261
265
  used_in: Used in
262
266
  metafields:
263
267
  none_configured_for: No metafield definitions configured for %{resource}.
data/config/routes.rb CHANGED
@@ -133,6 +133,7 @@ Spree::Core::Engine.add_routes do
133
133
  resources :store_credits
134
134
  resources :orders, only: [:index]
135
135
  resources :checkouts, only: [:index]
136
+ resources :addresses, only: [:index], controller: 'user_addresses'
136
137
  resources :gift_cards
137
138
 
138
139
  collection do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0
4
+ version: 5.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vendo Connect Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-23 00:00:00.000000000 Z
11
+ date: 2026-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.6.0
19
+ version: 5.6.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 5.6.0
26
+ version: 5.6.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: active_link_to
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -385,6 +385,7 @@ files:
385
385
  - app/controllers/spree/admin/taxonomies_controller.rb
386
386
  - app/controllers/spree/admin/taxons_controller.rb
387
387
  - app/controllers/spree/admin/translations_controller.rb
388
+ - app/controllers/spree/admin/user_addresses_controller.rb
388
389
  - app/controllers/spree/admin/user_passwords_controller.rb
389
390
  - app/controllers/spree/admin/user_sessions_controller.rb
390
391
  - app/controllers/spree/admin/users_controller.rb
@@ -460,6 +461,7 @@ files:
460
461
  - app/javascript/spree/admin/controllers/highlight_controller.js
461
462
  - app/javascript/spree/admin/controllers/import_form_controller.js
462
463
  - app/javascript/spree/admin/controllers/media_form_controller.js
464
+ - app/javascript/spree/admin/controllers/metafield_form_controller.js
463
465
  - app/javascript/spree/admin/controllers/money_field_controller.js
464
466
  - app/javascript/spree/admin/controllers/multi_input_controller.js
465
467
  - app/javascript/spree/admin/controllers/multi_tom_select_controller.js
@@ -993,6 +995,7 @@ files:
993
995
  - app/views/spree/admin/tables/_table.html.erb
994
996
  - app/views/spree/admin/tables/_table_body.html.erb
995
997
  - app/views/spree/admin/tables/_table_row.html.erb
998
+ - app/views/spree/admin/tables/columns/_address_name.html.erb
996
999
  - app/views/spree/admin/tables/columns/_api_key_status.html.erb
997
1000
  - app/views/spree/admin/tables/columns/_api_key_type.html.erb
998
1001
  - app/views/spree/admin/tables/columns/_gift_card_status.html.erb
@@ -1054,6 +1057,7 @@ files:
1054
1057
  - app/views/spree/admin/translations/translation_rows/_textarea_row.html.erb
1055
1058
  - app/views/spree/admin/translations/translation_rows/_tinymce_row.html.erb
1056
1059
  - app/views/spree/admin/translations/update.turbo_stream.erb
1060
+ - app/views/spree/admin/user_addresses/index.html.erb
1057
1061
  - app/views/spree/admin/user_passwords/edit.html.erb
1058
1062
  - app/views/spree/admin/user_passwords/new.html.erb
1059
1063
  - app/views/spree/admin/user_sessions/new.html.erb
@@ -1187,9 +1191,9 @@ licenses:
1187
1191
  - BSD-3-Clause
1188
1192
  metadata:
1189
1193
  bug_tracker_uri: https://github.com/spree/spree/issues
1190
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0
1194
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.1
1191
1195
  documentation_uri: https://docs.spreecommerce.org/
1192
- source_code_uri: https://github.com/spree/spree/tree/v5.6.0
1196
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.1
1193
1197
  post_install_message:
1194
1198
  rdoc_options: []
1195
1199
  require_paths: