spree_admin 5.4.2 → 5.5.0.rc1
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/controllers/spree/admin/assets_controller.rb +7 -1
- data/app/controllers/spree/admin/bulk_operations_controller.rb +4 -0
- data/app/controllers/spree/admin/channels_controller.rb +13 -0
- data/app/controllers/spree/admin/invitations_controller.rb +1 -2
- data/app/controllers/spree/admin/products_controller.rb +8 -8
- data/app/controllers/spree/admin/resource_controller.rb +11 -2
- data/app/controllers/spree/admin/states_controller.rb +13 -0
- data/app/controllers/spree/admin/zones_controller.rb +0 -1
- data/app/helpers/spree/admin/assets_helper.rb +5 -1
- data/app/helpers/spree/admin/base_helper.rb +0 -8
- data/app/helpers/spree/admin/channels_helper.rb +14 -0
- data/app/helpers/spree/admin/json_preview_helper.rb +5 -0
- data/app/helpers/spree/admin/orders_helper.rb +0 -29
- data/app/helpers/spree/admin/publishing_helper.rb +73 -0
- data/app/helpers/spree/admin/turbo_helper.rb +0 -18
- data/app/helpers/spree/admin/webhook_endpoints_helper.rb +1 -0
- data/app/javascript/spree/admin/application.js +4 -0
- data/app/javascript/spree/admin/controllers/product_form_controller.js +0 -26
- data/app/javascript/spree/admin/controllers/product_publishing_controller.js +11 -0
- data/app/javascript/spree/admin/controllers/slug_form_controller.js +18 -1
- data/app/javascript/spree/admin/controllers/zone_state_select_controller.js +31 -0
- data/app/presenters/spree/admin/order_summary_presenter.rb +12 -0
- data/app/views/spree/admin/api_keys/_form.html.erb +18 -0
- data/app/views/spree/admin/assets/edit.html.erb +64 -9
- data/app/views/spree/admin/channels/_form.html.erb +19 -0
- data/app/views/spree/admin/channels/edit.html.erb +1 -0
- data/app/views/spree/admin/channels/index.html.erb +12 -0
- data/app/views/spree/admin/channels/new.html.erb +1 -0
- data/app/views/spree/admin/gift_cards/_form.html.erb +1 -1
- data/app/views/spree/admin/integrations/_integration.html.erb +7 -9
- data/app/views/spree/admin/integrations/edit.html.erb +1 -1
- data/app/views/spree/admin/invitations/expired.html.erb +4 -0
- data/app/views/spree/admin/orders/_line_item.html.erb +3 -3
- data/app/views/spree/admin/orders/_shipment.html.erb +1 -1
- data/app/views/spree/admin/products/_form.html.erb +2 -1
- data/app/views/spree/admin/products/form/_publishing.html.erb +125 -0
- data/app/views/spree/admin/products/form/_status.html.erb +3 -47
- data/app/views/spree/admin/shared/_media_asset.html.erb +1 -1
- data/app/views/spree/admin/variants/edit.html.erb +0 -1
- data/app/views/spree/admin/zones/_state_members.html.erb +14 -4
- data/config/initializers/spree_admin_navigation.rb +9 -0
- data/config/initializers/spree_admin_tables.rb +62 -2
- data/config/locales/en.yml +32 -3
- data/config/routes.rb +11 -1
- data/lib/spree/admin/engine.rb +2 -0
- data/lib/spree/admin/tailwind_helper.rb +1 -1
- metadata +21 -11
- data/app/helpers/spree/admin/modal_helper.rb +0 -31
- data/app/views/spree/admin/shared/_modal.html.erb +0 -27
- /data/app/views/spree/admin/promotion_rules/forms/{_taxon.html.erb → _category.html.erb} +0 -0
- /data/app/views/spree/admin/promotion_rules/forms/{_user.html.erb → _customer.html.erb} +0 -0
- /data/app/views/spree/admin/promotion_rules/forms/{_user_logged_in.html.erb → _customer_logged_in.html.erb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f4bbbb1dd33ca70b641c0c4c687f4a8a36731ec816c3324930b44139d911bd6
|
|
4
|
+
data.tar.gz: d402abe10df4c9fe520db4f9b97fb17e33172238ff7e58b14f71b2754a9c09a5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9931e85d7ade12bc71fd556f9014cb410ad03e205eedc6ba4c1bc03d3e5d38a32ddabd7a5b34114fe0cb1b58059d7b6ce36907c3515a866812609c9911989517
|
|
7
|
+
data.tar.gz: 8f9090d2f5d25c761f4f3ce74701fabd179b9947c21ce23ab64362ac67363ce77b63c624136a35bb02b2897d60fe72e716ac4e8539fdc032f5bc7c7cdbcab12c
|
|
@@ -38,8 +38,14 @@ module Spree
|
|
|
38
38
|
|
|
39
39
|
private
|
|
40
40
|
|
|
41
|
+
# Includes :variant_ids so the variant-assignment checkboxes mass-assign
|
|
42
|
+
# straight into Spree::Asset#variant_ids= — the model resolves prefixed
|
|
43
|
+
# ids and rejects cross-product variants.
|
|
41
44
|
def permitted_resource_params
|
|
42
|
-
params.require(:asset).permit(
|
|
45
|
+
params.require(:asset).permit(
|
|
46
|
+
*Spree::PermittedAttributes.asset_attributes,
|
|
47
|
+
variant_ids: []
|
|
48
|
+
)
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
def create_turbo_stream_enabled?
|
|
@@ -63,8 +63,7 @@ module Spree
|
|
|
63
63
|
redirect_to spree.new_admin_admin_user_path(token: @invitation.token), status: :see_other
|
|
64
64
|
end
|
|
65
65
|
rescue ActiveRecord::RecordNotFound
|
|
66
|
-
|
|
67
|
-
nil
|
|
66
|
+
render :expired, status: :not_found
|
|
68
67
|
end
|
|
69
68
|
|
|
70
69
|
# PUT /admin/invitations/:id/accept
|
|
@@ -21,7 +21,7 @@ module Spree
|
|
|
21
21
|
new_action.before :build_master_stock_items
|
|
22
22
|
edit_action.before :build_master_prices
|
|
23
23
|
edit_action.before :build_master_stock_items
|
|
24
|
-
create.after :
|
|
24
|
+
create.after :assign_session_uploaded_assets
|
|
25
25
|
update.before :skip_updating_status
|
|
26
26
|
update.before :update_status
|
|
27
27
|
update.before :remove_empty_params
|
|
@@ -185,7 +185,7 @@ module Spree
|
|
|
185
185
|
@product_variant_ids[variant.human_name] = variant.id.to_s
|
|
186
186
|
@product_variant_prefix_ids[variant.human_name] = variant.to_param
|
|
187
187
|
|
|
188
|
-
image = variant.primary_media
|
|
188
|
+
image = variant.primary_media || @product.primary_media
|
|
189
189
|
if image.present? && image.attached? && image.variable?
|
|
190
190
|
@product_variant_images[variant.human_name] = helpers.spree_image_url(image, variant: :mini)
|
|
191
191
|
end
|
|
@@ -287,15 +287,15 @@ module Spree
|
|
|
287
287
|
{}
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
-
def
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
uploaded_assets = session_uploaded_assets('Spree::Variant')
|
|
290
|
+
def assign_session_uploaded_assets
|
|
291
|
+
uploaded_assets = session_uploaded_assets('Spree::Product')
|
|
294
292
|
|
|
295
293
|
return if uploaded_assets.empty?
|
|
296
294
|
|
|
297
|
-
uploaded_assets.update_all(viewable_id: @product.
|
|
298
|
-
|
|
295
|
+
uploaded_assets.update_all(viewable_id: @product.id, viewable_type: 'Spree::Product', updated_at: Time.current)
|
|
296
|
+
@product.update_thumbnail!
|
|
297
|
+
|
|
298
|
+
clear_session_for_uploaded_assets('Spree::Product')
|
|
299
299
|
end
|
|
300
300
|
|
|
301
301
|
def check_slug_availability
|
|
@@ -279,8 +279,7 @@ class Spree::Admin::ResourceController < Spree::Admin::BaseController
|
|
|
279
279
|
|
|
280
280
|
params[:q][:s] ||= collection_default_sort if collection_default_sort.present?
|
|
281
281
|
|
|
282
|
-
|
|
283
|
-
date_range_params.each do |param|
|
|
282
|
+
%i[created_at_gt updated_at_gt].each do |param|
|
|
284
283
|
if params[:q][param].present?
|
|
285
284
|
params[:q][param] = begin
|
|
286
285
|
params[:q][param].to_date&.in_time_zone(current_timezone)&.beginning_of_day
|
|
@@ -290,6 +289,16 @@ class Spree::Admin::ResourceController < Spree::Admin::BaseController
|
|
|
290
289
|
end
|
|
291
290
|
end
|
|
292
291
|
|
|
292
|
+
%i[created_at_lt updated_at_lt].each do |param|
|
|
293
|
+
if params[:q][param].present?
|
|
294
|
+
params[:q][param] = begin
|
|
295
|
+
params[:q][param].to_date&.in_time_zone(current_timezone)&.end_of_day
|
|
296
|
+
rescue StandardError
|
|
297
|
+
''
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
293
302
|
params[:q]
|
|
294
303
|
end
|
|
295
304
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class StatesController < ResourceController
|
|
4
|
+
belongs_to 'spree/country', find_by: :id
|
|
5
|
+
|
|
6
|
+
def select_options
|
|
7
|
+
states = @country.states.accessible_by(current_ability).order(:name)
|
|
8
|
+
|
|
9
|
+
render json: states.to_tom_select_json
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -3,7 +3,11 @@ module Spree
|
|
|
3
3
|
module AssetsHelper
|
|
4
4
|
def media_form_assets(viewable, viewable_type)
|
|
5
5
|
if viewable&.persisted?
|
|
6
|
-
|
|
6
|
+
# Product#images delegates to the master variant (legacy alias), so
|
|
7
|
+
# 5.5+ product-level uploads wouldn't show. gallery_media returns
|
|
8
|
+
# product-level media when present and falls back to legacy
|
|
9
|
+
# variant-pinned images during the transition.
|
|
10
|
+
viewable.respond_to?(:gallery_media) ? viewable.gallery_media : viewable.images
|
|
7
11
|
elsif session_uploaded_assets(viewable_type).any?
|
|
8
12
|
Spree::Asset.accessible_by(current_ability, :manage).where(id: session_uploaded_assets(viewable_type))
|
|
9
13
|
else
|
|
@@ -36,14 +36,6 @@ module Spree
|
|
|
36
36
|
@settings_area.present?
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def settings_active?
|
|
40
|
-
Spree::Deprecation.warn('settings_active? is deprecated and will be removed in Spree 5.5. Please use settings_area? instead')
|
|
41
|
-
@settings_active || %w[admin_users audits custom_domains exports invitations oauth_applications
|
|
42
|
-
payment_methods refund_reasons reimbursement_types return_authorization_reasons roles
|
|
43
|
-
shipping_categories shipping_methods stock_locations store_credit_categories
|
|
44
|
-
stores tax_categories tax_rates webhooks webhooks_subscribers zones policies metafield_definitions].include?(controller_name) || settings_area?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
39
|
# @return [Array<String>] the available countries for checkout
|
|
48
40
|
def available_countries_iso
|
|
49
41
|
@available_countries_iso ||= current_store.countries_available_for_checkout.pluck(:iso)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module ChannelsHelper
|
|
4
|
+
# Registered +Spree::OrderRouting::Strategy::Base+ subclasses presented in
|
|
5
|
+
# the channel edit form, sourced from +Spree.order_routing.strategies+ so the
|
|
6
|
+
# picker can never drift from what the model accepts. A blank value clears the
|
|
7
|
+
# channel-level override and falls back to
|
|
8
|
+
# +Store#preferred_order_routing_strategy+.
|
|
9
|
+
def channel_order_routing_strategy_options
|
|
10
|
+
Spree.order_routing.strategies.map { |strategy| [strategy.display_name, strategy.to_s] }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -69,6 +69,11 @@ module Spree
|
|
|
69
69
|
return Spree.api.public_send(method_name)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
if namespace == 'Spree::Api::V3::Admin'
|
|
73
|
+
method_name = "admin_#{class_name.underscore}_serializer"
|
|
74
|
+
return Spree.api.public_send(method_name) if Spree.api.respond_to?(method_name)
|
|
75
|
+
end
|
|
76
|
+
|
|
72
77
|
# Fall back to direct constant lookup
|
|
73
78
|
serializer_class_name = "#{namespace}::#{class_name}Serializer"
|
|
74
79
|
serializer_class_name.safe_constantize
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Admin
|
|
3
3
|
module OrdersHelper
|
|
4
|
-
TaxLine = Struct.new(:label, :display_amount, :item, :for_shipment, keyword_init: true) do
|
|
5
|
-
def name
|
|
6
|
-
Spree::Deprecation.warn("TaxLine is deprecated and will be removed in Spree 5.5")
|
|
7
|
-
item_name = item.name
|
|
8
|
-
item_name += " #{Spree.t(:shipment).downcase}" if for_shipment
|
|
9
|
-
|
|
10
|
-
"#{label} (#{item_name})"
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def order_summary_tax_lines_additional(order)
|
|
15
|
-
Spree::Deprecation.warn("order_summary_tax_lines_additional is deprecated and will be removed in Spree 5.5")
|
|
16
|
-
line_item_taxes = order.line_item_adjustments.tax.map { |tax_adjustment| map_to_tax_line(tax_adjustment) }
|
|
17
|
-
shipment_taxes = order.shipment_adjustments.tax.map { |tax_adjustment| map_to_tax_line(tax_adjustment, for_shipment: true) }
|
|
18
|
-
|
|
19
|
-
line_item_taxes + shipment_taxes
|
|
20
|
-
end
|
|
21
|
-
|
|
22
4
|
def order_payment_state(order, options = {})
|
|
23
5
|
return if order.payment_state.blank?
|
|
24
6
|
|
|
@@ -140,17 +122,6 @@ module Spree
|
|
|
140
122
|
'' => 'Transaction failed because wrong CVV2 number was entered or no CVV2 number was entered'
|
|
141
123
|
}
|
|
142
124
|
end
|
|
143
|
-
|
|
144
|
-
private
|
|
145
|
-
|
|
146
|
-
def map_to_tax_line(tax_adjustment, for_shipment: false)
|
|
147
|
-
TaxLine.new(
|
|
148
|
-
label: tax_adjustment.label,
|
|
149
|
-
display_amount: tax_adjustment.display_amount,
|
|
150
|
-
item: tax_adjustment.adjustable,
|
|
151
|
-
for_shipment: for_shipment
|
|
152
|
-
)
|
|
153
|
-
end
|
|
154
125
|
end
|
|
155
126
|
end
|
|
156
127
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Admin
|
|
5
|
+
# Helpers for the product Publishing card in the Rails admin. Mirrors the
|
|
6
|
+
# SPA's publishing card (see packages/dashboard/src/components/spree/products/publishing-card.tsx):
|
|
7
|
+
# per-channel status is gated by product status — Draft/Archived products
|
|
8
|
+
# render every publication as +not_available+ regardless of window.
|
|
9
|
+
module PublishingHelper
|
|
10
|
+
# @param product_status [String] the product's status (draft/active/archived)
|
|
11
|
+
# @param publication [Spree::ProductPublication]
|
|
12
|
+
# @return [Symbol] one of :live, :scheduled, :hidden, :not_available
|
|
13
|
+
def publication_schedule_status(product_status, publication)
|
|
14
|
+
return :not_available unless product_status == 'active'
|
|
15
|
+
|
|
16
|
+
now = Time.current
|
|
17
|
+
return :hidden if publication.unpublished_at && publication.unpublished_at <= now
|
|
18
|
+
return :scheduled if publication.published_at && publication.published_at > now
|
|
19
|
+
|
|
20
|
+
:live
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Renders a colored dot + status label for a publication row.
|
|
24
|
+
def publication_status_badge(product_status, publication)
|
|
25
|
+
status = publication_schedule_status(product_status, publication)
|
|
26
|
+
dot_class = case status
|
|
27
|
+
when :live then 'bg-success'
|
|
28
|
+
when :scheduled then 'bg-warning'
|
|
29
|
+
else 'bg-secondary'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
label = Spree.t("admin.publishing.status_#{status}")
|
|
33
|
+
|
|
34
|
+
content_tag(:span, class: 'd-inline-flex align-items-center gap-1 text-muted small') do
|
|
35
|
+
content_tag(:span, '', class: "publication-dot rounded-circle #{dot_class}",
|
|
36
|
+
style: 'display:inline-block; width:0.5rem; height:0.5rem;') +
|
|
37
|
+
content_tag(:span, label)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# One-line summary of the publication window in the store's timezone.
|
|
42
|
+
def publication_caption(product_status, publication, store)
|
|
43
|
+
status = publication_schedule_status(product_status, publication)
|
|
44
|
+
tz = ActiveSupport::TimeZone[store.preferred_timezone] || Time.zone
|
|
45
|
+
|
|
46
|
+
case status
|
|
47
|
+
when :not_available
|
|
48
|
+
Spree.t('admin.publishing.caption_not_available',
|
|
49
|
+
product_status: Spree.t("admin.products.status_options.#{product_status}", default: product_status.to_s.humanize))
|
|
50
|
+
when :hidden
|
|
51
|
+
Spree.t('admin.publishing.caption_unpublished',
|
|
52
|
+
date: l(publication.unpublished_at.in_time_zone(tz), format: :short))
|
|
53
|
+
when :scheduled
|
|
54
|
+
if publication.unpublished_at
|
|
55
|
+
Spree.t('admin.publishing.caption_window',
|
|
56
|
+
start: l(publication.published_at.in_time_zone(tz), format: :short),
|
|
57
|
+
end: l(publication.unpublished_at.in_time_zone(tz), format: :short))
|
|
58
|
+
else
|
|
59
|
+
Spree.t('admin.publishing.caption_scheduled',
|
|
60
|
+
date: l(publication.published_at.in_time_zone(tz), format: :short))
|
|
61
|
+
end
|
|
62
|
+
else # :live
|
|
63
|
+
if publication.unpublished_at
|
|
64
|
+
Spree.t('admin.publishing.caption_hidden_after',
|
|
65
|
+
date: l(publication.unpublished_at.in_time_zone(tz), format: :short))
|
|
66
|
+
else
|
|
67
|
+
Spree.t('admin.publishing.caption_live')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Admin
|
|
3
3
|
module TurboHelper
|
|
4
|
-
def turbo_close_modal(modal_id = nil)
|
|
5
|
-
Spree::Deprecation.warn('turbo_close_modal is deprecated and will be removed in Spree 5.5. Use turbo_close_dialog instead.')
|
|
6
|
-
|
|
7
|
-
modal_id ||= 'modal'
|
|
8
|
-
|
|
9
|
-
turbo_stream.replace :modal_scripts do
|
|
10
|
-
turbo_frame_tag :modal_scripts do
|
|
11
|
-
javascript_tag do
|
|
12
|
-
raw <<~JS
|
|
13
|
-
if (document.querySelector('##{modal_id}')) {
|
|
14
|
-
window.$("##{modal_id}").modal('hide');
|
|
15
|
-
}
|
|
16
|
-
JS
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
4
|
def turbo_close_dialog
|
|
23
5
|
turbo_stream.replace 'main-dialog' do
|
|
24
6
|
render 'spree/admin/shared/dialog'
|
|
@@ -68,6 +68,7 @@ import OrderBillingAddressController from 'spree/admin/controllers/order_billing
|
|
|
68
68
|
import PageBuilderController from 'spree/admin/controllers/page_builder_controller'
|
|
69
69
|
import PasswordToggle from 'spree/admin/controllers/password_toggle_controller'
|
|
70
70
|
import ProductFormController from 'spree/admin/controllers/product_form_controller'
|
|
71
|
+
import ProductPublishingController from 'spree/admin/controllers/product_publishing_controller'
|
|
71
72
|
import QueryBuilderController from 'spree/admin/controllers/query_builder_controller'
|
|
72
73
|
import RangeInputController from 'spree/admin/controllers/range_input_controller'
|
|
73
74
|
import TableController from 'spree/admin/controllers/table_controller'
|
|
@@ -92,6 +93,7 @@ import TooltipController from 'spree/admin/controllers/tooltip_controller'
|
|
|
92
93
|
import TurboSubmitButtonController from 'spree/admin/controllers/turbo_submit_button_controller'
|
|
93
94
|
import UnitSystemController from 'spree/admin/controllers/unit_system_controller'
|
|
94
95
|
import VariantsFormController from 'spree/admin/controllers/variants_form_controller'
|
|
96
|
+
import ZoneStateSelectController from 'spree/admin/controllers/zone_state_select_controller'
|
|
95
97
|
import BulkEditorController from 'spree/admin/controllers/bulk_editor_controller'
|
|
96
98
|
import AddressAutocompleteController from 'spree/core/controllers/address_autocomplete_controller'
|
|
97
99
|
import AddressFormController from 'spree/core/controllers/address_form_controller'
|
|
@@ -139,6 +141,7 @@ application.register('page-builder', PageBuilderController)
|
|
|
139
141
|
application.register('password-toggle', PasswordToggle)
|
|
140
142
|
application.register('password-visibility', PasswordVisibility)
|
|
141
143
|
application.register('product-form', ProductFormController)
|
|
144
|
+
application.register('product-publishing', ProductPublishingController)
|
|
142
145
|
application.register('query-builder', QueryBuilderController)
|
|
143
146
|
application.register('range-input', RangeInputController)
|
|
144
147
|
application.register('table', TableController)
|
|
@@ -166,6 +169,7 @@ application.register('turbo-submit-button', TurboSubmitButtonController)
|
|
|
166
169
|
application.register('textarea-autogrow', TextareaAutogrow)
|
|
167
170
|
application.register('unit-system', UnitSystemController)
|
|
168
171
|
application.register('variants-form', VariantsFormController)
|
|
172
|
+
application.register('zone-state-select', ZoneStateSelectController)
|
|
169
173
|
application.register('bulk-editor', BulkEditorController)
|
|
170
174
|
|
|
171
175
|
LocalTime.start()
|
|
@@ -4,10 +4,6 @@ export default class extends Controller {
|
|
|
4
4
|
static targets = [
|
|
5
5
|
'trackInventoryCheckbox',
|
|
6
6
|
'quantityForm',
|
|
7
|
-
'availableOn',
|
|
8
|
-
'makeActiveAt',
|
|
9
|
-
'discontinueOn',
|
|
10
|
-
'status',
|
|
11
7
|
'pricesForm'
|
|
12
8
|
]
|
|
13
9
|
|
|
@@ -26,28 +22,6 @@ export default class extends Controller {
|
|
|
26
22
|
this.togglePricesFormVisibility()
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
switchAvailabilityDatesFields(event) {
|
|
30
|
-
let status = event.target.value
|
|
31
|
-
if (status === 'draft') {
|
|
32
|
-
this.show(this.availableOnTarget)
|
|
33
|
-
this.show(this.makeActiveAtTarget)
|
|
34
|
-
} else if (status === 'active') {
|
|
35
|
-
this.show(this.availableOnTarget)
|
|
36
|
-
this.hide(this.makeActiveAtTarget)
|
|
37
|
-
} else {
|
|
38
|
-
this.hide(this.availableOnTarget)
|
|
39
|
-
this.hide(this.makeActiveAtTarget)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
show(element) {
|
|
44
|
-
element.classList.remove('hidden', 'd-none')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
hide(element) {
|
|
48
|
-
element.classList.add('hidden')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
25
|
toggleQuantityFormVisibility() {
|
|
52
26
|
if (this.hasQuantityFormTarget) {
|
|
53
27
|
if (!this.hasVariantsValue && this.trackInventoryCheckboxTarget.checked) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
// Toggles the "Manage" channel-checkbox panel on the product Publishing card.
|
|
4
|
+
// Pairs with spree/admin/app/views/spree/admin/products/form/_publishing.html.erb.
|
|
5
|
+
export default class extends Controller {
|
|
6
|
+
static targets = ['manage']
|
|
7
|
+
|
|
8
|
+
toggleManage() {
|
|
9
|
+
this.manageTarget.classList.toggle('d-none')
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -6,9 +6,26 @@ export default class extends Controller {
|
|
|
6
6
|
'url',
|
|
7
7
|
]
|
|
8
8
|
|
|
9
|
+
connect() {
|
|
10
|
+
this.urlTouched = this.hasUrlTarget && this.urlTarget.value.length > 0
|
|
11
|
+
if (this.hasUrlTarget) {
|
|
12
|
+
this.urlTarget.addEventListener('input', () => { this.urlTouched = true })
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
updateUrlFromName() {
|
|
17
|
+
if (this.urlTouched) return
|
|
10
18
|
const name = this.nameTarget.value
|
|
11
|
-
|
|
19
|
+
// Mirrors ActiveSupport's +String#parameterize+: NFKD decompose, drop
|
|
20
|
+
// combining marks (accents), lowercase, hyphenate the rest. Keeps the
|
|
21
|
+
// legacy admin's auto-fill aligned with what +normalizes :code+ stores
|
|
22
|
+
// server-side — without NFKD, "Café" rendered "caf-" instead of "cafe".
|
|
23
|
+
const url = name
|
|
24
|
+
.normalize('NFKD')
|
|
25
|
+
.replace(/\p{M}/gu, '')
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
28
|
+
.replace(/(^-|-$)+/g, '')
|
|
12
29
|
this.urlTarget.value = url
|
|
13
30
|
}
|
|
14
31
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { get } from '@rails/request.js'
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
url: String
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static targets = ["statesSelect"]
|
|
10
|
+
|
|
11
|
+
async countryChanged(event) {
|
|
12
|
+
const countryId = event.target.value
|
|
13
|
+
const statesInput = this.statesSelectTarget.querySelector('select')
|
|
14
|
+
if (!statesInput || !statesInput.tomselect) return
|
|
15
|
+
|
|
16
|
+
const tomSelect = statesInput.tomselect
|
|
17
|
+
tomSelect.clear()
|
|
18
|
+
tomSelect.clearOptions()
|
|
19
|
+
|
|
20
|
+
if (!countryId) return
|
|
21
|
+
|
|
22
|
+
const url = this.urlValue.replace(':country_id', countryId)
|
|
23
|
+
const response = await get(url, { contentType: 'application/json' })
|
|
24
|
+
|
|
25
|
+
if (response.ok) {
|
|
26
|
+
const states = await response.json
|
|
27
|
+
states.forEach(state => tomSelect.addOption(state))
|
|
28
|
+
tomSelect.refreshOptions(false)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -13,6 +13,7 @@ module Spree
|
|
|
13
13
|
[
|
|
14
14
|
*metadata_rows,
|
|
15
15
|
:separator,
|
|
16
|
+
channel_row,
|
|
16
17
|
market_row,
|
|
17
18
|
locale_row,
|
|
18
19
|
currency_row,
|
|
@@ -73,6 +74,17 @@ module Spree
|
|
|
73
74
|
rows
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
def channel_row
|
|
78
|
+
return nil if order.channel.blank?
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
label: Spree.t(:channel),
|
|
82
|
+
value: order.channel.name,
|
|
83
|
+
id: 'channel',
|
|
84
|
+
link: Spree::Core::Engine.routes.url_helpers.edit_admin_channel_path(order.channel)
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
76
88
|
def market_row
|
|
77
89
|
return nil if order.market.blank?
|
|
78
90
|
|
|
@@ -24,3 +24,21 @@
|
|
|
24
24
|
<% end %>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
|
+
|
|
28
|
+
<div class="card mb-6" id="api-key-scopes" data-secret-only data-show-when-key-type="secret">
|
|
29
|
+
<div class="card-header">
|
|
30
|
+
<h5 class="card-title"><%= Spree.t('admin.api_keys.scopes', default: 'Scopes') %></h5>
|
|
31
|
+
<p class="text-sm text-gray-500 mt-1">
|
|
32
|
+
<%= Spree.t('admin.api_keys.scopes_description', default: 'Required for secret keys. Pick the narrowest set of scopes your integration needs.') %>
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="card-body">
|
|
36
|
+
<% Spree::ApiKey::SCOPES.each do |scope| %>
|
|
37
|
+
<div class="custom-control form-checkbox mb-1">
|
|
38
|
+
<%= f.check_box :scopes, { multiple: true, class: 'custom-control-input', id: "api_key_scopes_#{scope}" }, scope, nil %>
|
|
39
|
+
<%= f.label "scopes_#{scope}", scope, class: 'custom-control-label font-mono text-sm' %>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
<%= f.hidden_field :scopes, value: '', multiple: true %>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
@@ -1,17 +1,72 @@
|
|
|
1
|
-
<%= turbo_frame_tag :
|
|
2
|
-
<%= dialog_header(Spree.t(:edit) + ' ' + Spree.t(:image)) %>
|
|
1
|
+
<%= turbo_frame_tag :drawer do %>
|
|
3
2
|
<%= form_with model: @asset, url: spree.admin_asset_path(@asset), method: :put, scope: :asset do |f| %>
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
<%= drawer_header(Spree.t(:edit) + ' ' + Spree.t(:image)) %>
|
|
4
|
+
|
|
5
|
+
<div class="drawer-body">
|
|
6
|
+
<% if @asset.attachment.attached? %>
|
|
7
|
+
<% blob_url_opts = { host: request.host_with_port, protocol: (request.ssl? ? 'https' : 'http') } %>
|
|
8
|
+
<% download_url = Rails.application.routes.url_helpers.rails_blob_url(@asset.attachment.blob, **blob_url_opts, disposition: 'attachment') %>
|
|
9
|
+
<% original_url = Rails.application.routes.url_helpers.rails_blob_url(@asset.attachment.blob, **blob_url_opts) %>
|
|
10
|
+
<div class="mb-4 rounded-lg border bg-light overflow-hidden">
|
|
11
|
+
<a href="<%= original_url %>"
|
|
12
|
+
target="_blank"
|
|
13
|
+
rel="noopener"
|
|
14
|
+
title="<%= Spree.t(:view_full_size) %>"
|
|
15
|
+
style="display: block; position: relative; cursor: zoom-in;">
|
|
16
|
+
<%= spree_image_tag(@asset.attachment, variant: :large, alt: @asset.alt, class: 'w-full max-h-96 object-contain') %>
|
|
17
|
+
<span style="position: absolute; top: 8px; right: 8px; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 6px; background: rgba(255,255,255,0.95); color: #4b5563; box-shadow: 0 1px 2px rgba(0,0,0,0.1); pointer-events: none;">
|
|
18
|
+
<%= icon('zoom-in', class: 'mr-0') %>
|
|
19
|
+
</span>
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="flex gap-2 mb-4">
|
|
23
|
+
<%= link_to original_url, target: '_blank', rel: 'noopener', class: 'btn btn-light btn-sm' do %>
|
|
24
|
+
<%= icon('external-link', class: 'mr-1') %><%= Spree.t(:view_full_size) %>
|
|
25
|
+
<% end %>
|
|
26
|
+
<%= link_to download_url, target: '_blank', rel: 'noopener', class: 'btn btn-light btn-sm' do %>
|
|
27
|
+
<%= icon('download', class: 'mr-1') %><%= Spree.t(:download) %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
<% else %>
|
|
31
|
+
<%= f.spree_file_field :attachment, width: 200, height: 200, can_delete: false, label: Spree.t(:image) %>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<%= f.spree_text_area :alt, label: Spree.t(:alt_text), rows: 3 %>
|
|
35
|
+
|
|
36
|
+
<%# Variant assignment shows only for product-level assets — variant-pinned assets
|
|
37
|
+
already live on a single variant via the polymorphic viewable, so the
|
|
38
|
+
join-table UI doesn't apply. Empty submission (no boxes ticked) clears
|
|
39
|
+
all links thanks to the hidden field below. %>
|
|
40
|
+
<% if @asset.viewable_type == 'Spree::Product' && @asset.viewable.present? && @asset.viewable.variants.any? %>
|
|
41
|
+
<% product = @asset.viewable %>
|
|
42
|
+
<% linked_ids = @asset.variant_media.pluck(:variant_id).to_set %>
|
|
43
|
+
<div class="mt-4 pt-4 border-t">
|
|
44
|
+
<label class="form-label"><%= Spree.t(:assigned_variants) %></label>
|
|
45
|
+
<p class="text-muted small mb-2">
|
|
46
|
+
<%= Spree.t(:assigned_variants_help) %>
|
|
47
|
+
</p>
|
|
48
|
+
<%= hidden_field_tag 'asset[variant_ids][]', '' %>
|
|
49
|
+
<div class="flex flex-col gap-1">
|
|
50
|
+
<% product.variants.each do |variant| %>
|
|
51
|
+
<% chip_id = "asset_variant_#{variant.id}" %>
|
|
52
|
+
<div class="custom-control form-checkbox rounded-lg hover:bg-gray-100 p-2 transition-colors duration-100 ease-linear">
|
|
53
|
+
<%= check_box_tag('asset[variant_ids][]', variant.to_param, linked_ids.include?(variant.id), id: chip_id, class: 'custom-control-input') %>
|
|
54
|
+
<%= label_tag chip_id, class: 'custom-control-label' do %>
|
|
55
|
+
<%= variant.descriptive_name.presence || variant.sku %>
|
|
56
|
+
<% end %>
|
|
57
|
+
</div>
|
|
58
|
+
<% end %>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<% end %>
|
|
6
62
|
</div>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
</div>
|
|
10
|
-
<div class="dialog-footer">
|
|
63
|
+
|
|
64
|
+
<div class="drawer-footer gap-3">
|
|
11
65
|
<%= turbo_save_button_tag %>
|
|
66
|
+
<%= drawer_discard_button %>
|
|
12
67
|
<%= link_to Spree.t('actions.destroy'), object_url(@asset),
|
|
13
68
|
data: { turbo_method: :delete, turbo_confirm: Spree.t(:are_you_sure_delete), turbo_frame: '_top' },
|
|
14
69
|
class: 'btn btn-danger ml-auto' if can?(:destroy, @asset) %>
|
|
15
70
|
</div>
|
|
16
71
|
<% end %>
|
|
17
|
-
<% end %>
|
|
72
|
+
<% end %>
|