spree_admin 5.6.0.rc3 → 5.6.0.rc4
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/app/helpers/spree/admin/api_keys_helper.rb +4 -0
- data/app/javascript/spree/admin/application.js +2 -0
- data/app/javascript/spree/admin/controllers/api_key_form_controller.js +24 -0
- data/app/services/spree/products/prepare_nested_attributes.rb +265 -0
- data/app/views/spree/admin/api_keys/_details.html.erb +7 -0
- data/app/views/spree/admin/api_keys/_form.html.erb +82 -52
- data/config/locales/en.yml +4 -0
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d4b0b49721fed24adb001b68fa29aff946374186d3de5a2d3eac7500d13dc1d
|
|
4
|
+
data.tar.gz: c44a54d9688235ac082e358b267446c565fdab88d91a0f23519df95bd0a95c4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b021c5154b33e6600b2ae72c8cf92d505dcda0486a012fe3959b5509c7cb5285e3966fc7690b7c9ed45d010a4d2a1ef5602249f5e62f012fbcbc2c03a8f943fe
|
|
7
|
+
data.tar.gz: 2372e776ff93374313cde1d2749e6bf9d05947e640a1e4d1a2f1a66240bc109e9ffb5838d3df3dc82c5d80a6a443a05f6657ef82186a68d5b5879b46b491ad91
|
|
@@ -9,6 +9,10 @@ module Spree
|
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def api_key_channel_options
|
|
13
|
+
current_store.channels.active.pluck(:name, :id)
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
def api_key_status_badge(api_key)
|
|
13
17
|
if api_key.active?
|
|
14
18
|
content_tag(:span, icon('check') + Spree.t('admin.api_keys.statuses.active'), class: 'badge badge-success')
|
|
@@ -47,6 +47,7 @@ import AutocompleteSelectController from 'spree/admin/controllers/autocomplete_s
|
|
|
47
47
|
import AutoScrollController from 'spree/admin/controllers/auto_scroll_controller'
|
|
48
48
|
import BetterSliderController from 'spree/admin/controllers/better_slider_controller'
|
|
49
49
|
import BlockFormController from 'spree/admin/controllers/block_form_controller'
|
|
50
|
+
import ApiKeyFormController from 'spree/admin/controllers/api_key_form_controller'
|
|
50
51
|
import BulkOperationController from 'spree/admin/controllers/bulk_operation_controller'
|
|
51
52
|
import CalculatorFieldsController from 'spree/admin/controllers/calculator_fields_controller'
|
|
52
53
|
import CalendarRangeController from 'spree/admin/controllers/calendar_range_controller'
|
|
@@ -103,6 +104,7 @@ import EnableButtonController from 'spree/core/controllers/enable_button_control
|
|
|
103
104
|
|
|
104
105
|
application.register('active-storage-upload', ActiveStorageUpload)
|
|
105
106
|
application.register('address-autocomplete', AddressAutocompleteController)
|
|
107
|
+
application.register('api-key-form', ApiKeyFormController)
|
|
106
108
|
application.register('address-form', AddressFormController)
|
|
107
109
|
application.register('admin', AdminController)
|
|
108
110
|
application.register('asset-uploader', AssetUploaderController)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
// Progressive disclosure for the API key form: scopes apply only to secret
|
|
4
|
+
// keys, channel binding only to publishable keys. Hidden sections get their
|
|
5
|
+
// inputs disabled so the irrelevant params are never submitted.
|
|
6
|
+
export default class extends Controller {
|
|
7
|
+
static targets = ['typeSelect', 'section']
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.toggle()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
toggle() {
|
|
14
|
+
const type = this.typeSelectTarget.value
|
|
15
|
+
|
|
16
|
+
this.sectionTargets.forEach((section) => {
|
|
17
|
+
const match = section.dataset.showWhenKeyType === type
|
|
18
|
+
section.hidden = !match
|
|
19
|
+
section.querySelectorAll('select, input').forEach((input) => {
|
|
20
|
+
input.disabled = !match
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Products
|
|
3
|
+
# Prepares nested attributes for product updates, handling multi-store scenarios
|
|
4
|
+
# and permissions.
|
|
5
|
+
#
|
|
6
|
+
# This service ensures that when editing a product in one store, taxon associations
|
|
7
|
+
# from other stores are preserved. This prevents accidental data loss when a store
|
|
8
|
+
# admin updates product categories in their store without affecting other stores.
|
|
9
|
+
#
|
|
10
|
+
# Variant removal is opt-in: only variants explicitly listed in the
|
|
11
|
+
# `removed_variant_ids` param are marked for destruction (or collected for
|
|
12
|
+
# discontinuation when they have completed orders). Variants merely absent from
|
|
13
|
+
# `variants_attributes` are left untouched, so a partially rendered or broken
|
|
14
|
+
# form can never silently mass-delete variants.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# service = Spree::Products::PrepareNestedAttributes.new(
|
|
18
|
+
# product,
|
|
19
|
+
# current_store,
|
|
20
|
+
# params,
|
|
21
|
+
# current_ability
|
|
22
|
+
# )
|
|
23
|
+
# prepared_params = service.call
|
|
24
|
+
#
|
|
25
|
+
class PrepareNestedAttributes
|
|
26
|
+
attr_reader :variants_to_discontinue
|
|
27
|
+
|
|
28
|
+
def initialize(product, store, params, ability)
|
|
29
|
+
@product = product
|
|
30
|
+
@store = store
|
|
31
|
+
@params = params
|
|
32
|
+
@ability = ability
|
|
33
|
+
@variants_to_discontinue = []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call
|
|
37
|
+
extract_removed_variant_ids
|
|
38
|
+
|
|
39
|
+
if params[:variants_attributes]
|
|
40
|
+
params[:variants_attributes].each do |key, variant_params|
|
|
41
|
+
existing_variant = variant_params[:id].presence && @product.variants.find_by(id: variant_params[:id])
|
|
42
|
+
# a re-submitted variant always wins over a removal request
|
|
43
|
+
variants_to_remove.delete(variant_params[:id].to_s) if variant_params[:id].present?
|
|
44
|
+
|
|
45
|
+
variant_params.delete(:price) # remove legacy price param
|
|
46
|
+
|
|
47
|
+
if can_update_prices?
|
|
48
|
+
backfill_price_ids!(variant_params, existing_variant)
|
|
49
|
+
|
|
50
|
+
variant_params[:prices_attributes]&.each do |price_key, price_params|
|
|
51
|
+
variant_params[:prices_attributes][price_key]['_destroy'] = '1' if price_params[:amount].blank?
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
variant_params.delete(:prices_attributes)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
variant_params[:option_value_variants_attributes] = update_option_value_variants(variant_params.delete(:options), existing_variant)
|
|
58
|
+
|
|
59
|
+
variant_params.delete(:stock_items_attributes) unless can_update_stock_items?
|
|
60
|
+
|
|
61
|
+
params[:variants_attributes].delete(key) if variant_params.blank?
|
|
62
|
+
end
|
|
63
|
+
params[:variants_attributes] = params[:variants_attributes].merge(removed_variants_attributes)
|
|
64
|
+
|
|
65
|
+
params[:product_option_types_attributes] = product_option_types_params.merge(removed_product_option_types_attributes)
|
|
66
|
+
elsif params[:master_attributes]
|
|
67
|
+
params[:master_attributes].delete(:stock_items_attributes) unless can_update_stock_items?
|
|
68
|
+
|
|
69
|
+
if can_update_prices?
|
|
70
|
+
# If the master price is nil then mark it for destruction
|
|
71
|
+
params.dig(:master_attributes, :prices_attributes)&.each do |price_key, price_params|
|
|
72
|
+
params[:master_attributes][:prices_attributes][price_key]['_destroy'] = '1' if price_params[:amount].blank?
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
params[:master_attributes].delete(:prices_attributes)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
params.delete(:legacy_product_publications_attributes) unless can?(:manage, Spree::ProductPublication)
|
|
80
|
+
|
|
81
|
+
# ensure the product is owned by a store
|
|
82
|
+
params[:store_id] = store.id if params[:store_id].blank? && product.store_id.blank?
|
|
83
|
+
|
|
84
|
+
# The variants matrix was emptied: no variant rows re-submitted, removals sent instead.
|
|
85
|
+
# Option types are detached only when the removal list covers every variant, so a
|
|
86
|
+
# partial (possibly broken) submission never turns the product into a simple one.
|
|
87
|
+
# Variants kept alive by discontinuation still count as removed from the matrix,
|
|
88
|
+
# so the detachment can't hinge on any of them yielding a `_destroy` row.
|
|
89
|
+
if params[:variants_attributes].blank? && variants_to_remove.any? && can_remove_variants?
|
|
90
|
+
attributes = removed_variants_attributes
|
|
91
|
+
|
|
92
|
+
params[:option_type_ids] = [] if removing_all_variants? && !params.key?(:option_type_ids)
|
|
93
|
+
|
|
94
|
+
if attributes.any?
|
|
95
|
+
params[:variants_attributes] = attributes
|
|
96
|
+
params[:variants_attributes].permit!
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
params
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
attr_reader :product, :store, :params, :ability
|
|
106
|
+
|
|
107
|
+
delegate :can?, :cannot?, to: :ability
|
|
108
|
+
|
|
109
|
+
# Backfill IDs for prices_attributes entries that reference existing prices
|
|
110
|
+
# so that ActiveRecord updates them instead of inserting duplicates
|
|
111
|
+
def backfill_price_ids!(variant_params, existing_variant)
|
|
112
|
+
return unless existing_variant && variant_params[:prices_attributes]
|
|
113
|
+
|
|
114
|
+
variant_params[:prices_attributes].each do |_key, price_params|
|
|
115
|
+
next if price_params[:id].present?
|
|
116
|
+
next if price_params[:currency].blank?
|
|
117
|
+
|
|
118
|
+
existing_price = existing_variant.prices.base_prices.find_by(currency: price_params[:currency])
|
|
119
|
+
price_params[:id] = existing_price.id if existing_price
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def product_option_types_params
|
|
124
|
+
@product_option_types_params ||= {}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def product_option_types_to_remove
|
|
128
|
+
@product_option_types_to_remove ||= product.product_option_type_ids
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Pulls `removed_variant_ids` out of the params so it never reaches Product#update.
|
|
132
|
+
# Must run before any `variants_to_remove` access.
|
|
133
|
+
def extract_removed_variant_ids
|
|
134
|
+
@removed_variant_ids = Array(params.delete(:removed_variant_ids)).map(&:to_s)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Only variants the client explicitly asked to remove, and only ones that
|
|
138
|
+
# actually belong to this product.
|
|
139
|
+
def variants_to_remove
|
|
140
|
+
@variants_to_remove ||= (@removed_variant_ids || []).uniq & all_variant_ids
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def all_variant_ids
|
|
144
|
+
@all_variant_ids ||= product.variant_ids.map(&:to_s)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def removing_all_variants?
|
|
148
|
+
(all_variant_ids - variants_to_remove).empty?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def can_update_prices?
|
|
152
|
+
@can_update_prices ||= product.new_record? || can?(:manage, Spree::Price.new(variant_id: product.default_variant.id))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def can_manage_option_types?
|
|
156
|
+
@can_manage_option_types ||= product.new_record? || can?(:manage_option_types, product)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def can_update_stock_items?
|
|
160
|
+
@can_update_stock_items ||= product.new_record? || can?(:manage, Spree::StockItem.new(variant_id: product.default_variant.id))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def can_remove_variants?
|
|
164
|
+
@can_remove_variants ||= product.persisted? && can?(:destroy, product.default_variant)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def removed_variants_attributes
|
|
168
|
+
return {} unless can_remove_variants?
|
|
169
|
+
|
|
170
|
+
populate_variants_to_discontinue
|
|
171
|
+
|
|
172
|
+
attributes = {}
|
|
173
|
+
last_index = params[:variants_attributes].presence&.keys&.map(&:to_i)&.max || -1
|
|
174
|
+
variant_ids_to_destroy.each_with_index do |variant_id, index|
|
|
175
|
+
attributes[(last_index + 1 + index).to_s] = { id: variant_id, _destroy: '1' }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
attributes
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def populate_variants_to_discontinue
|
|
182
|
+
ids = variants_to_remove.select { |vid| variant_ids_with_completed_orders.include?(vid) }
|
|
183
|
+
@variants_to_discontinue = product.variants.where(id: ids).to_a if ids.any?
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def variant_ids_to_destroy
|
|
187
|
+
variants_to_remove - variant_ids_with_completed_orders
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def variant_ids_with_completed_orders
|
|
191
|
+
@variant_ids_with_completed_orders ||=
|
|
192
|
+
product.variants
|
|
193
|
+
.joins(:orders)
|
|
194
|
+
.merge(Spree::Order.complete)
|
|
195
|
+
.reorder(nil)
|
|
196
|
+
.distinct
|
|
197
|
+
.pluck(:id)
|
|
198
|
+
.map(&:to_s)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def removed_product_option_types_attributes
|
|
202
|
+
return {} unless can_manage_option_types?
|
|
203
|
+
|
|
204
|
+
attributes = {}
|
|
205
|
+
last_index = product_option_types_params.keys.map(&:to_i).max
|
|
206
|
+
product_option_types_to_remove.each_with_index do |product_option_type_id, index|
|
|
207
|
+
attributes[(last_index + index + 1).to_s] = { id: product_option_type_id, _destroy: '1' }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
attributes
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def update_option_value_variants(option_value_params, existing_variant)
|
|
214
|
+
return {} unless option_value_params.present?
|
|
215
|
+
return {} unless can_manage_option_types?
|
|
216
|
+
|
|
217
|
+
option_value_variant_params = {}
|
|
218
|
+
|
|
219
|
+
option_value_params.each_with_index do |opt, index|
|
|
220
|
+
option_type = Spree::OptionType.find_by_param(opt[:id]) if opt.fetch(:id)
|
|
221
|
+
option_type ||= Spree::OptionType.where(name: opt[:name].parameterize).first_or_initialize do |o|
|
|
222
|
+
o.name = o.presentation = opt[:name]
|
|
223
|
+
o.position = opt[:position]
|
|
224
|
+
o.save!
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
option_value_identificator = if opt[:option_value_name].present?
|
|
228
|
+
opt[:option_value_name]
|
|
229
|
+
else
|
|
230
|
+
opt[:option_value_presentation]
|
|
231
|
+
end.parameterize.strip
|
|
232
|
+
|
|
233
|
+
option_value = option_type.option_values.where(name: option_value_identificator).first_or_initialize do |o|
|
|
234
|
+
o.presentation = opt[:option_value_presentation]
|
|
235
|
+
o.save!
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
existing_option_value_variant = existing_variant&.option_value_variants&.find { |ovv| ovv.option_value_id == option_value.id }
|
|
239
|
+
|
|
240
|
+
option_value_variant_params[index.to_s] = { id: existing_option_value_variant&.id, option_value_id: option_value.id }.compact_blank
|
|
241
|
+
|
|
242
|
+
next if product_option_types_params.find { |_i, v| v[:option_type_id] == option_type.id }
|
|
243
|
+
|
|
244
|
+
existing_product_option_type = @product.product_option_types.find { |pot| pot.option_type_id == option_type.id }
|
|
245
|
+
|
|
246
|
+
if existing_product_option_type
|
|
247
|
+
product_option_types_to_remove.delete(existing_product_option_type.id)
|
|
248
|
+
product_option_types_params[opt[:position]] = {
|
|
249
|
+
id: existing_product_option_type.id,
|
|
250
|
+
position: opt[:position],
|
|
251
|
+
option_type_id: option_type.id
|
|
252
|
+
}
|
|
253
|
+
else
|
|
254
|
+
product_option_types_params[opt[:position]] = {
|
|
255
|
+
option_type_id: option_type.id,
|
|
256
|
+
position: opt[:position]
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
option_value_variant_params
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
<dd class="mt-1"><%= api_key_type_badge(@api_key) %></dd>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
+
<% if @api_key.channel.present? %>
|
|
18
|
+
<div>
|
|
19
|
+
<dt class="text-sm text-gray-500"><%= Spree.t('admin.api_keys.channel') %></dt>
|
|
20
|
+
<dd class="mt-1"><span class="badge badge-light"><%= @api_key.channel.name %></span></dd>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
17
24
|
<div>
|
|
18
25
|
<dt class="text-sm text-gray-500"><%= Spree.t(:created_at) %></dt>
|
|
19
26
|
<dd class="mt-1"><%= l(@api_key.created_at, format: :long) %></dd>
|
|
@@ -1,60 +1,90 @@
|
|
|
1
|
-
<div
|
|
2
|
-
<div class="card-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
<div data-controller="api-key-form">
|
|
2
|
+
<div class="card mb-6">
|
|
3
|
+
<div class="card-body">
|
|
4
|
+
<%= f.spree_text_field :name, required: true, autofocus: f.object.new_record?,
|
|
5
|
+
placeholder: Spree.t('admin.api_keys.name_placeholder') %>
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
<% if f.object.new_record? %>
|
|
8
|
+
<%= f.spree_select :key_type, api_key_type_options,
|
|
9
|
+
{ prompt: Spree.t(:select_key_type), label: Spree.t(:type) },
|
|
10
|
+
{ required: true,
|
|
11
|
+
data: { api_key_form_target: 'typeSelect', action: 'change->api-key-form#toggle' } } %>
|
|
12
|
+
<p class="text-sm text-gray-500 mt-2">
|
|
13
|
+
<strong><%= Spree.t('admin.api_keys.key_types.publishable') %>:</strong>
|
|
14
|
+
<%= Spree.t('admin.api_keys.key_type_descriptions.publishable') %>
|
|
15
|
+
<br>
|
|
16
|
+
<strong><%= Spree.t('admin.api_keys.key_types.secret') %>:</strong>
|
|
17
|
+
<%= Spree.t('admin.api_keys.key_type_descriptions.secret') %>
|
|
18
|
+
</p>
|
|
19
|
+
<% else %>
|
|
20
|
+
<div class="form-group">
|
|
21
|
+
<label class="form-label"><%= Spree.t(:type) %></label>
|
|
22
|
+
<div class="form-control-plaintext">
|
|
23
|
+
<%= api_key_type_badge(@api_key) %>
|
|
24
|
+
</div>
|
|
22
25
|
</div>
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
25
28
|
</div>
|
|
26
|
-
</div>
|
|
27
29
|
|
|
28
|
-
<% if f.object.new_record? %>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
<% if f.object.new_record? %>
|
|
31
|
+
<div class="card mb-6" id="api-key-scopes" hidden
|
|
32
|
+
data-api-key-form-target="section" data-show-when-key-type="secret">
|
|
33
|
+
<div class="card-header">
|
|
34
|
+
<h5 class="card-title"><%= Spree.t('admin.api_keys.scopes') %></h5>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="card-body">
|
|
37
|
+
<p class="text-sm text-gray-500 mb-4">
|
|
38
|
+
<%= Spree.t('admin.api_keys.scopes_description') %>
|
|
39
|
+
</p>
|
|
40
|
+
<% Spree::ApiKey::SCOPES.each do |scope| %>
|
|
41
|
+
<div class="custom-control form-checkbox mb-1">
|
|
42
|
+
<%= f.check_box :scopes, { multiple: true, class: 'custom-control-input', id: "api_key_scopes_#{scope}" }, scope, nil %>
|
|
43
|
+
<%= f.label "scopes_#{scope}", scope, class: 'custom-control-label font-mono text-sm' %>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
<%= f.hidden_field :scopes, value: '', multiple: true %>
|
|
47
|
+
</div>
|
|
35
48
|
</div>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
|
|
50
|
+
<div class="card mb-6" id="api-key-channel" hidden
|
|
51
|
+
data-api-key-form-target="section" data-show-when-key-type="publishable">
|
|
52
|
+
<div class="card-header">
|
|
53
|
+
<h5 class="card-title"><%= Spree.t('admin.api_keys.channel') %></h5>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="card-body">
|
|
56
|
+
<p class="text-sm text-gray-500 mb-4">
|
|
57
|
+
<%= Spree.t('admin.api_keys.channel_description') %>
|
|
58
|
+
</p>
|
|
59
|
+
<%= f.spree_select :channel_id, api_key_channel_options,
|
|
60
|
+
{ include_blank: Spree.t('admin.api_keys.all_channels'), label: false } %>
|
|
61
|
+
</div>
|
|
44
62
|
</div>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
<% elsif f.object.secret? %>
|
|
64
|
+
<div class="card mb-6">
|
|
65
|
+
<div class="card-header">
|
|
66
|
+
<h5 class="card-title"><%= Spree.t('admin.api_keys.scopes') %></h5>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="card-body">
|
|
69
|
+
<p class="text-sm text-gray-500 mb-4">
|
|
70
|
+
<%= Spree.t('admin.api_keys.scopes_immutable') %>
|
|
71
|
+
</p>
|
|
72
|
+
<% f.object.scopes.each do |scope| %>
|
|
73
|
+
<span class="badge badge-light font-mono text-sm me-1 mb-1"><%= scope %></span>
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
53
76
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
<% elsif f.object.channel.present? %>
|
|
78
|
+
<div class="card mb-6">
|
|
79
|
+
<div class="card-header">
|
|
80
|
+
<h5 class="card-title"><%= Spree.t('admin.api_keys.channel') %></h5>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="card-body">
|
|
83
|
+
<p class="text-sm text-gray-500 mb-4">
|
|
84
|
+
<%= Spree.t('admin.api_keys.channel_immutable') %>
|
|
85
|
+
</p>
|
|
86
|
+
<span class="badge badge-light"><%= f.object.channel.name %></span>
|
|
87
|
+
</div>
|
|
58
88
|
</div>
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
<% end %>
|
|
90
|
+
</div>
|
data/config/locales/en.yml
CHANGED
|
@@ -46,6 +46,10 @@ en:
|
|
|
46
46
|
origin_settings: Origin Settings
|
|
47
47
|
amount_spent: Amount spent
|
|
48
48
|
api_keys:
|
|
49
|
+
all_channels: All channels (store-wide)
|
|
50
|
+
channel: Channel
|
|
51
|
+
channel_description: Optional. Bind this key to a single channel — a bound key always resolves that channel and rejects requests naming any other. Leave on "All channels" for a store-wide key.
|
|
52
|
+
channel_immutable: The channel binding is fixed when the key is created. To change it, create a new key bound to the channel you need and revoke this one.
|
|
49
53
|
example_request: Example Request
|
|
50
54
|
key_revoked_message: This API key has been revoked and can no longer be used.
|
|
51
55
|
key_type_descriptions:
|
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.0.rc4
|
|
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-
|
|
11
|
+
date: 2026-07-20 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.0.rc4
|
|
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.0.rc4
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: active_link_to
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -438,6 +438,7 @@ files:
|
|
|
438
438
|
- app/javascript/spree/admin/controllers/active_storage_upload_controller.js
|
|
439
439
|
- app/javascript/spree/admin/controllers/address_form_controller.js
|
|
440
440
|
- app/javascript/spree/admin/controllers/admin_controller.js
|
|
441
|
+
- app/javascript/spree/admin/controllers/api_key_form_controller.js
|
|
441
442
|
- app/javascript/spree/admin/controllers/asset_uploader_controller.js
|
|
442
443
|
- app/javascript/spree/admin/controllers/auto_scroll_controller.js
|
|
443
444
|
- app/javascript/spree/admin/controllers/autocomplete_select_controller.js
|
|
@@ -514,6 +515,7 @@ files:
|
|
|
514
515
|
- app/models/spree/admin/table/visibility.rb
|
|
515
516
|
- app/models/spree/admin/updater.rb
|
|
516
517
|
- app/presenters/spree/admin/order_summary_presenter.rb
|
|
518
|
+
- app/services/spree/products/prepare_nested_attributes.rb
|
|
517
519
|
- app/subscribers/spree/admin/import_row_subscriber.rb
|
|
518
520
|
- app/subscribers/spree/admin/import_subscriber.rb
|
|
519
521
|
- app/views/action_text/video_embeds/_thumbnail.html.erb
|
|
@@ -1185,9 +1187,9 @@ licenses:
|
|
|
1185
1187
|
- BSD-3-Clause
|
|
1186
1188
|
metadata:
|
|
1187
1189
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
1188
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.
|
|
1190
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc4
|
|
1189
1191
|
documentation_uri: https://docs.spreecommerce.org/
|
|
1190
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.6.0.
|
|
1192
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc4
|
|
1191
1193
|
post_install_message:
|
|
1192
1194
|
rdoc_options: []
|
|
1193
1195
|
require_paths:
|