spree_admin 5.4.3 → 5.5.0.rc2
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/concerns/spree/admin/auth_rate_limiting.rb +58 -0
- data/app/controllers/spree/admin/assets_controller.rb +7 -1
- data/app/controllers/spree/admin/channels_controller.rb +13 -0
- data/app/controllers/spree/admin/products_controller.rb +8 -8
- data/app/controllers/spree/admin/user_passwords_controller.rb +4 -0
- data/app/controllers/spree/admin/user_sessions_controller.rb +4 -0
- 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 +2 -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/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/integrations/_integration.html.erb +7 -9
- data/app/views/spree/admin/integrations/edit.html.erb +1 -1
- data/app/views/spree/admin/product_translations/index.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/config/initializers/spree_admin_navigation.rb +9 -0
- data/config/initializers/spree_admin_tables.rb +60 -0
- data/config/locales/en.yml +32 -3
- data/config/routes.rb +1 -0
- data/lib/spree/admin/engine.rb +2 -0
- data/lib/spree/admin/runtime_configuration.rb +5 -0
- metadata +19 -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
|
@@ -5,29 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
<div id="integration-<%= integration_class.integration_key %>" class="col mb-6 flex items-stretch">
|
|
7
7
|
<div class="card w-full">
|
|
8
|
-
<div class="card-header border-b flex
|
|
8
|
+
<div class="card-header border-b flex items-center justify-center h-auto pr-4 <% if integration.present? %>bg-green-50<% end %>">
|
|
9
9
|
<div class="flex items-center py-1">
|
|
10
10
|
<%= image_tag integration_class.icon_path, alt: integration_name, height: 36 if integration_class.icon_path.present? %>
|
|
11
11
|
</div>
|
|
12
|
-
|
|
13
|
-
<% if integration.present? %>
|
|
14
|
-
<span class="badge badge-success small">
|
|
15
|
-
<%= icon('check', class: 'mr-1') %>
|
|
16
|
-
<%= Spree.t(:active) %>
|
|
17
|
-
</span>
|
|
18
|
-
<% end %>
|
|
19
12
|
</div>
|
|
20
13
|
|
|
21
14
|
<div class="card-body">
|
|
22
15
|
<p><%= integration_description %></p>
|
|
23
16
|
</div>
|
|
24
17
|
|
|
25
|
-
<div class="card-footer flex <% if integration.present? %>items-
|
|
18
|
+
<div class="card-footer flex <% if integration.present? %>items-center gap-3<% else %>flex-col<% end %>">
|
|
26
19
|
<% if integration.present? %>
|
|
27
20
|
<%= link_to spree.edit_admin_integration_path(integration), class: 'btn btn-sm btn-light' do %>
|
|
28
21
|
<%= icon 'settings' %>
|
|
29
22
|
<%= Spree.t(:settings) %>
|
|
30
23
|
<% end if can?(:update, integration) %>
|
|
24
|
+
|
|
25
|
+
<span class="ml-auto inline-flex items-center text-xs font-medium text-green-700">
|
|
26
|
+
<span class="w-1.5 h-1.5 rounded-full bg-green-500 mr-1"></span>
|
|
27
|
+
<%= Spree.t(:active) %>
|
|
28
|
+
</span>
|
|
31
29
|
<% else %>
|
|
32
30
|
<%= link_to_with_icon 'plus', "#{Spree.t('actions.connect')} #{integration_class.integration_name}", spree.new_admin_integration_path(integration: { type: integration_class.name }), class: 'btn btn-primary truncate w-full' if can?(:create, Spree::Integration) %>
|
|
33
31
|
<% end %>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @integration } %>
|
|
7
7
|
|
|
8
|
-
<%= form_for @integration, as: :integration, url: spree.admin_integration_path(@integration) do |form| %>
|
|
8
|
+
<%= form_for @integration, as: :integration, url: spree.admin_integration_path(@integration), html: { id: "edit_integration_#{@integration.id}" } do |form| %>
|
|
9
9
|
<div class="grid grid-cols-12 gap-6">
|
|
10
10
|
<div class="col-span-12 lg:col-span-6 lg:col-start-4">
|
|
11
11
|
<%= render "spree/admin/integrations/forms/#{@integration.key}", form: form %>
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
<%= render 'spree/admin/shared/index_table_options', collection: @products %>
|
|
102
102
|
</div>
|
|
103
103
|
<% else %>
|
|
104
|
-
<%= render 'spree/admin/shared/no_resource_found', new_object_url: nil %>
|
|
104
|
+
<%= render 'spree/admin/shared/no_resource_found', new_object_url: nil, model_class: Spree::Product %>
|
|
105
105
|
<% end %>
|
|
106
106
|
</div>
|
|
107
107
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="grid grid-cols-12 gap-6" data-controller="product-form slug-form seo-form" data-seo-form-editor-value="tinymce">
|
|
2
2
|
<div class="col-span-12 md:col-span-8">
|
|
3
3
|
<%= render 'spree/admin/products/form/base', f: f %>
|
|
4
|
-
<%= render 'spree/admin/shared/media_form', viewable: @product
|
|
4
|
+
<%= render 'spree/admin/shared/media_form', viewable: @product, viewable_type: 'Spree::Product' %>
|
|
5
5
|
<%= f.fields_for :master, @product.master do |vf| %>
|
|
6
6
|
<%= render 'spree/admin/variants/form/pricing', f: vf %>
|
|
7
7
|
<% end unless @product.has_variants? %>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
<div class="col-span-12 md:col-span-4">
|
|
13
13
|
<%= render 'spree/admin/products/form/status', f: f %>
|
|
14
|
+
<%= render 'spree/admin/products/form/publishing', f: f %>
|
|
14
15
|
<%= render 'spree/admin/products/form/categorization', f: f %>
|
|
15
16
|
<%= render 'spree/admin/products/form/shipping', f: f %>
|
|
16
17
|
<%= render 'spree/admin/products/form/tax', f: f %>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
Publishing card — mirrors the SPA's <PublishingCard> in
|
|
3
|
+
packages/dashboard/src/components/spree/products/publishing-card.tsx.
|
|
4
|
+
|
|
5
|
+
All channel attach/detach AND per-channel scheduling submit through the
|
|
6
|
+
same nested-attributes form (+legacy_product_publications_attributes+). Each row
|
|
7
|
+
in the Manage panel toggles the +_destroy+ flag on the corresponding
|
|
8
|
+
publication; unchecked rows for unattached channels send blank attrs that
|
|
9
|
+
the model's +reject_if+ discards.
|
|
10
|
+
|
|
11
|
+
Datetime inputs render values in the store's timezone since datetime-local
|
|
12
|
+
has no TZ awareness (same pattern as the legacy form/_status partial).
|
|
13
|
+
%>
|
|
14
|
+
|
|
15
|
+
<%
|
|
16
|
+
store_timezone = ActiveSupport::TimeZone[current_store.preferred_timezone] || Time.zone
|
|
17
|
+
to_store_local = ->(value) { value&.in_time_zone(store_timezone)&.strftime('%Y-%m-%dT%H:%M') }
|
|
18
|
+
tz_help = Spree.t('admin.datetime_field_timezone_help', zone: store_timezone.name)
|
|
19
|
+
store_channels = current_store.channels.order(:name).to_a
|
|
20
|
+
publications_by_channel = @product.product_publications.includes(:channel).index_by(&:channel_id)
|
|
21
|
+
|
|
22
|
+
# Build one record per store channel: the existing publication when
|
|
23
|
+
# attached, or a fresh ProductPublication when not. Stable channel-keyed
|
|
24
|
+
# indices keep the form params deterministic so checkbox toggles always
|
|
25
|
+
# reference the right slot.
|
|
26
|
+
channel_rows = store_channels.map do |channel|
|
|
27
|
+
publication = publications_by_channel[channel.id] ||
|
|
28
|
+
@product.product_publications.build(channel: channel)
|
|
29
|
+
[channel, publication]
|
|
30
|
+
end
|
|
31
|
+
%>
|
|
32
|
+
|
|
33
|
+
<div class="card mb-6" data-controller="product-publishing">
|
|
34
|
+
<div class="card-header d-flex align-items-center justify-content-between">
|
|
35
|
+
<h6 class="card-title mb-0"><%= Spree.t('admin.publishing.title') %></h6>
|
|
36
|
+
<% if store_channels.any? %>
|
|
37
|
+
<button type="button"
|
|
38
|
+
class="btn btn-sm btn-outline-secondary"
|
|
39
|
+
data-action="product-publishing#toggleManage">
|
|
40
|
+
<%= Spree.t('admin.publishing.manage_cta') %>
|
|
41
|
+
</button>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="card-body">
|
|
46
|
+
<% if store_channels.empty? %>
|
|
47
|
+
<p class="text-xs text-muted mb-0"><%= Spree.t('admin.publishing.no_channels') %></p>
|
|
48
|
+
<% else %>
|
|
49
|
+
<%# ---- Manage panel (hidden by default) ---- %>
|
|
50
|
+
<div class="d-none flex flex-col gap-2 mb-3" data-product-publishing-target="manage">
|
|
51
|
+
<p class="text-xs text-muted mb-1"><%= Spree.t('admin.publishing.manage_description') %></p>
|
|
52
|
+
<% channel_rows.each_with_index do |(channel, publication), index| %>
|
|
53
|
+
<%= f.fields_for :legacy_product_publications, publication, child_index: index do |pf| %>
|
|
54
|
+
<%= pf.hidden_field :id if publication.persisted? %>
|
|
55
|
+
<%= pf.hidden_field :channel_id, value: channel.id %>
|
|
56
|
+
<% checked_by_default = publication.persisted? || (@product.new_record? && channel.default?) %>
|
|
57
|
+
<label class="flex items-center gap-3 rounded border px-3 py-2 cursor-pointer hover:bg-gray-50 text-sm">
|
|
58
|
+
<%# The +_destroy+ hidden field defaults to 1 (= destroy/skip);
|
|
59
|
+
the checkbox flips it to 0 (= keep/upsert) when checked.
|
|
60
|
+
+include_hidden: false+ on the checkbox keeps Rails from
|
|
61
|
+
emitting its own competing hidden field. %>
|
|
62
|
+
<%= pf.hidden_field :_destroy, value: '1', id: nil %>
|
|
63
|
+
<%= pf.check_box :_destroy,
|
|
64
|
+
{ checked: checked_by_default,
|
|
65
|
+
class: 'form-check-input m-0',
|
|
66
|
+
include_hidden: false },
|
|
67
|
+
'0', '1' %>
|
|
68
|
+
<span class="flex-1"><%= channel.name %></span>
|
|
69
|
+
<% unless channel.active? %>
|
|
70
|
+
<span class="text-xs text-muted"><%= Spree.t('admin.publishing.inactive_marker') %></span>
|
|
71
|
+
<% end %>
|
|
72
|
+
</label>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<%# ---- Per-publication scheduling rows ---- %>
|
|
78
|
+
<% if publications_by_channel.empty? %>
|
|
79
|
+
<p class="text-xs text-muted mb-0"><%= Spree.t('admin.publishing.empty') %></p>
|
|
80
|
+
<% else %>
|
|
81
|
+
<% channel_rows.each_with_index do |(channel, publication), index| %>
|
|
82
|
+
<% next unless publication.persisted? %>
|
|
83
|
+
<%= f.fields_for :legacy_product_publications, publication, child_index: index do |pf| %>
|
|
84
|
+
<details class="publication-row group rounded -mx-2 px-2 hover:bg-gray-50">
|
|
85
|
+
<summary class="flex items-start justify-between gap-3 py-2 cursor-pointer" style="list-style: none;">
|
|
86
|
+
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
87
|
+
<div class="flex items-center gap-2">
|
|
88
|
+
<span class="truncate text-sm font-medium"><%= channel.name %></span>
|
|
89
|
+
<%= publication_status_badge(@product.status, publication) %>
|
|
90
|
+
</div>
|
|
91
|
+
<span class="text-xs text-muted">
|
|
92
|
+
<%= publication_caption(@product.status, publication, current_store) %>
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
<span class="shrink-0 mt-1 opacity-0 transition-opacity group-hover:opacity-100 text-muted">
|
|
96
|
+
<%= icon('pencil', class: 'h-3.5 w-3.5') %>
|
|
97
|
+
</span>
|
|
98
|
+
</summary>
|
|
99
|
+
<div class="mt-2 ps-2 pb-2 flex flex-col gap-3">
|
|
100
|
+
<div>
|
|
101
|
+
<%= pf.label :published_at,
|
|
102
|
+
Spree.t('admin.publishing.published_at_label'),
|
|
103
|
+
class: 'form-label text-xs mb-1' %>
|
|
104
|
+
<%= pf.datetime_field :published_at,
|
|
105
|
+
value: to_store_local.call(publication.published_at),
|
|
106
|
+
class: 'form-control form-control-sm' %>
|
|
107
|
+
<small class="text-muted text-xs"><%= tz_help %></small>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<%= pf.label :unpublished_at,
|
|
111
|
+
Spree.t('admin.publishing.unpublished_at_label'),
|
|
112
|
+
class: 'form-label text-xs mb-1' %>
|
|
113
|
+
<%= pf.datetime_field :unpublished_at,
|
|
114
|
+
value: to_store_local.call(publication.unpublished_at),
|
|
115
|
+
class: 'form-control form-control-sm' %>
|
|
116
|
+
<small class="text-muted text-xs"><%= tz_help %></small>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</details>
|
|
120
|
+
<% end %>
|
|
121
|
+
<% end %>
|
|
122
|
+
<% end %>
|
|
123
|
+
<% end %>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
@@ -1,51 +1,7 @@
|
|
|
1
|
-
<%# datetime-local inputs have no timezone; render values in the store's timezone so admins see and submit values in the store's local time. %>
|
|
2
|
-
<%
|
|
3
|
-
store_timezone = ActiveSupport::TimeZone[current_store.preferred_timezone] || Time.zone
|
|
4
|
-
to_store_local = ->(value) { value&.in_time_zone(store_timezone)&.strftime('%Y-%m-%dT%H:%M') }
|
|
5
|
-
make_active_at_local = to_store_local.call(@product.make_active_at)
|
|
6
|
-
available_on_local = to_store_local.call(@product.available_on)
|
|
7
|
-
discontinue_on_local = to_store_local.call(@product.discontinue_on)
|
|
8
|
-
tz_help = Spree.t('admin.datetime_field_timezone_help', zone: store_timezone.name)
|
|
9
|
-
%>
|
|
10
|
-
|
|
11
1
|
<div class="card mb-6">
|
|
12
2
|
<div class="card-body">
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
available_status_options(@product),
|
|
17
|
-
{ selected: @product.status, help_bubble: show_product_status_help_bubble? ? Spree.t('admin.products.status_form.status') : nil },
|
|
18
|
-
{ data: {
|
|
19
|
-
action: 'change->product-form#switchAvailabilityDatesFields',
|
|
20
|
-
'product-form-target': 'status'
|
|
21
|
-
}
|
|
22
|
-
} %>
|
|
23
|
-
</div>
|
|
24
|
-
<div class="col-span-6 <%= 'hidden' if @product.active? %>" data-product-form-target="makeActiveAt">
|
|
25
|
-
<% if can?(:activate, @product) %>
|
|
26
|
-
<%= f.spree_datetime_field :make_active_at,
|
|
27
|
-
value: make_active_at_local,
|
|
28
|
-
help: tz_help,
|
|
29
|
-
help_bubble: Spree.t('admin.products.status_form.make_active_at'),
|
|
30
|
-
max: discontinue_on_local %>
|
|
31
|
-
<% end %>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
<div class="grid grid-cols-12 gap-6">
|
|
35
|
-
<div data-product-form-target="availableOn" class="col-span-6">
|
|
36
|
-
<%= f.spree_datetime_field :available_on,
|
|
37
|
-
value: available_on_local,
|
|
38
|
-
help: tz_help,
|
|
39
|
-
help_bubble: Spree.t('admin.products.status_form.available_on'),
|
|
40
|
-
max: discontinue_on_local %>
|
|
41
|
-
</div>
|
|
42
|
-
<div data-product-form-target="discontinueOn" class="col-span-6">
|
|
43
|
-
<%= f.spree_datetime_field :discontinue_on,
|
|
44
|
-
value: discontinue_on_local,
|
|
45
|
-
help: tz_help,
|
|
46
|
-
help_bubble: Spree.t('admin.products.status_form.discontinue_on'),
|
|
47
|
-
min: make_active_at_local %>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
3
|
+
<%= f.spree_select :status,
|
|
4
|
+
available_status_options(@product),
|
|
5
|
+
{ selected: @product.status, help_bubble: show_product_status_help_bubble? ? Spree.t('admin.products.status_form.status') : nil } %>
|
|
50
6
|
</div>
|
|
51
7
|
</div>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<button class="btn btn-light btn-sm px-1"><%= icon('grip-vertical', class: 'mr-0') %></button>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<%= link_to spree.edit_admin_asset_path(asset), data: { turbo_frame: '
|
|
10
|
+
<%= link_to spree.edit_admin_asset_path(asset), data: { turbo_frame: 'drawer', action: 'drawer#open' } do %>
|
|
11
11
|
<%= spree_image_tag(asset.attachment, width: 200, height: 200, alt: asset.alt, class: 'w-full block h-full absolute') %>
|
|
12
12
|
<% end %>
|
|
13
13
|
</div>
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
<%= render 'spree/admin/variants/form/basic', f: f %>
|
|
11
11
|
<%= render 'spree/admin/variants/form/pricing', f: f %>
|
|
12
12
|
<%= render 'spree/admin/variants/form/inventory', f: f %>
|
|
13
|
-
<%= render 'spree/admin/shared/media_form', viewable: @variant, viewable_type: 'Spree::Variant' %>
|
|
14
13
|
<%= render 'spree/admin/shared/edit_resource_links', f: f %>
|
|
15
14
|
</div>
|
|
16
15
|
</div>
|
|
@@ -251,6 +251,15 @@ Rails.application.config.after_initialize do
|
|
|
251
251
|
active: -> { controller_name == 'policies' },
|
|
252
252
|
if: -> { can?(:manage, Spree::Policy) }
|
|
253
253
|
|
|
254
|
+
# Channels
|
|
255
|
+
settings_nav.add :channels,
|
|
256
|
+
label: :channels,
|
|
257
|
+
url: :admin_channels_path,
|
|
258
|
+
icon: 'broadcast',
|
|
259
|
+
position: 65,
|
|
260
|
+
active: -> { controller_name == 'channels' },
|
|
261
|
+
if: -> { can?(:manage, Spree::Channel) }
|
|
262
|
+
|
|
254
263
|
# Payment Methods
|
|
255
264
|
settings_nav.add :payment_methods,
|
|
256
265
|
label: :payments,
|
|
@@ -128,6 +128,19 @@ Rails.application.config.after_initialize do
|
|
|
128
128
|
search_url: ->(view_context) { view_context.spree.admin_tags_select_options_path(format: :json, taggable_type: 'Spree::Product') },
|
|
129
129
|
method: ->(product) { product.tag_list.to_sentence }
|
|
130
130
|
|
|
131
|
+
Spree.admin.tables.products.add :channels,
|
|
132
|
+
label: :channels,
|
|
133
|
+
type: :association,
|
|
134
|
+
filter_type: :select,
|
|
135
|
+
sortable: false,
|
|
136
|
+
filterable: true,
|
|
137
|
+
default: false,
|
|
138
|
+
position: 90,
|
|
139
|
+
ransack_attribute: 'channels_id',
|
|
140
|
+
operators: %i[in not_in],
|
|
141
|
+
value_options: -> { Spree::Current.store&.channels&.order(:name)&.pluck(:name, :id)&.map { |name, id| { value: id, label: name } } || [] },
|
|
142
|
+
method: ->(product) { product.channels.pluck(:name).to_sentence }
|
|
143
|
+
|
|
131
144
|
# Products bulk actions
|
|
132
145
|
Spree.admin.tables.products.add_bulk_action :set_active,
|
|
133
146
|
label: 'admin.bulk_ops.products.title.set_active',
|
|
@@ -350,6 +363,19 @@ Rails.application.config.after_initialize do
|
|
|
350
363
|
operators: %i[in],
|
|
351
364
|
search_url: ->(view_context) { view_context.spree.select_options_admin_promotions_path(format: :json) }
|
|
352
365
|
|
|
366
|
+
Spree.admin.tables.orders.add :channel,
|
|
367
|
+
label: :channel,
|
|
368
|
+
type: :association,
|
|
369
|
+
filter_type: :select,
|
|
370
|
+
sortable: true,
|
|
371
|
+
filterable: true,
|
|
372
|
+
default: true,
|
|
373
|
+
position: 35,
|
|
374
|
+
ransack_attribute: 'channel_id',
|
|
375
|
+
operators: %i[eq not_eq in not_in],
|
|
376
|
+
value_options: -> { Spree::Current.store&.channels&.order(:name)&.pluck(:name, :id)&.map { |name, id| { value: id, label: name } } || [] },
|
|
377
|
+
method: ->(order) { order.channel&.name }
|
|
378
|
+
|
|
353
379
|
# Register Checkouts table (draft orders)
|
|
354
380
|
Spree.admin.tables.register(:checkouts, model_class: Spree::Order, search_param: :search, date_range_param: :created_at, new_resource: false)
|
|
355
381
|
|
|
@@ -1744,6 +1770,40 @@ Rails.application.config.after_initialize do
|
|
|
1744
1770
|
default: true,
|
|
1745
1771
|
position: 10
|
|
1746
1772
|
|
|
1773
|
+
# ==========================================
|
|
1774
|
+
# Register Channels table
|
|
1775
|
+
# ==========================================
|
|
1776
|
+
Spree.admin.tables.register(:channels, model_class: Spree::Channel, search_param: :name_cont)
|
|
1777
|
+
|
|
1778
|
+
Spree.admin.tables.channels.add :name,
|
|
1779
|
+
label: :name,
|
|
1780
|
+
type: :link,
|
|
1781
|
+
sortable: true,
|
|
1782
|
+
filterable: true,
|
|
1783
|
+
default: true,
|
|
1784
|
+
position: 10
|
|
1785
|
+
|
|
1786
|
+
Spree.admin.tables.channels.add :code,
|
|
1787
|
+
label: :code,
|
|
1788
|
+
sortable: true,
|
|
1789
|
+
default: true,
|
|
1790
|
+
position: 20
|
|
1791
|
+
|
|
1792
|
+
Spree.admin.tables.channels.add :active,
|
|
1793
|
+
label: :status,
|
|
1794
|
+
type: :status,
|
|
1795
|
+
sortable: false,
|
|
1796
|
+
default: true,
|
|
1797
|
+
position: 30,
|
|
1798
|
+
method: ->(c) { c.active? ? 'active' : 'inactive' }
|
|
1799
|
+
|
|
1800
|
+
Spree.admin.tables.channels.add :default,
|
|
1801
|
+
label: :default,
|
|
1802
|
+
type: :boolean,
|
|
1803
|
+
sortable: true,
|
|
1804
|
+
default: true,
|
|
1805
|
+
position: 40
|
|
1806
|
+
|
|
1747
1807
|
# ==========================================
|
|
1748
1808
|
# Register Refund Reasons table
|
|
1749
1809
|
# ==========================================
|
data/config/locales/en.yml
CHANGED
|
@@ -26,6 +26,8 @@ en:
|
|
|
26
26
|
revoked: API key has been revoked
|
|
27
27
|
revoked_at: Revoked at
|
|
28
28
|
revoked_by: Revoked by
|
|
29
|
+
scopes: Scopes
|
|
30
|
+
scopes_description: Required for secret keys. Pick the narrowest set of scopes your integration needs.
|
|
29
31
|
secret_key_hidden: This secret key cannot be revealed. If you've lost it, revoke this key and create a new one.
|
|
30
32
|
statuses:
|
|
31
33
|
active: Active
|
|
@@ -73,6 +75,13 @@ en:
|
|
|
73
75
|
title:
|
|
74
76
|
add_tags: Add tags
|
|
75
77
|
remove_tags: Remove tags
|
|
78
|
+
channels:
|
|
79
|
+
code_hint: A short, stable identifier (e.g. "online", "pos", "wholesale"). Used by API clients via the X-Spree-Channel header and for order attribution.
|
|
80
|
+
default: Default channel
|
|
81
|
+
default_hint: Exactly one channel per store is the default. Used as the fallback when no channel is selected and as the auto-publish target for new products.
|
|
82
|
+
order_routing_strategy: Order routing strategy
|
|
83
|
+
order_routing_strategy_hint: Overrides the store-level routing strategy for orders attributed to this channel.
|
|
84
|
+
order_routing_strategy_inherit: Inherit from store
|
|
76
85
|
checkout_settings:
|
|
77
86
|
checkout_links:
|
|
78
87
|
description: Links to these pages will be displayed in the checkout footer.
|
|
@@ -278,14 +287,34 @@ en:
|
|
|
278
287
|
seo:
|
|
279
288
|
placeholder: Add a title and description to see how this product might appear in a search engine listing
|
|
280
289
|
status_form:
|
|
281
|
-
available_on: Marks when the product will be released, put a future date to indicate that this is a pre-order
|
|
282
|
-
discontinue_on: Marks when the product should be automatically taken off from your site
|
|
283
|
-
make_active_at: Marks when the product should be automatically promoted to "active" state
|
|
284
290
|
status: Draft products aren't available for purchase. Active products are live.
|
|
291
|
+
status_options:
|
|
292
|
+
active: Active
|
|
293
|
+
archived: Archived
|
|
294
|
+
draft: Draft
|
|
285
295
|
stores:
|
|
286
296
|
choose_stores: Choose which stores this product should be available in
|
|
287
297
|
variants:
|
|
288
298
|
option_types_link: To add more option types please go to <a href="%{link}">Option Types</a>
|
|
299
|
+
publishing:
|
|
300
|
+
caption_hidden_after: Comes down %{date}
|
|
301
|
+
caption_live: Visible to customers now
|
|
302
|
+
caption_not_available: Hidden — product is %{product_status}
|
|
303
|
+
caption_scheduled: Goes live %{date}
|
|
304
|
+
caption_unpublished: Was hidden on %{date}
|
|
305
|
+
caption_window: Live %{start} → %{end}
|
|
306
|
+
empty: Not listed on any sales channel yet.
|
|
307
|
+
inactive_marker: Inactive
|
|
308
|
+
manage_cta: Manage
|
|
309
|
+
manage_description: Pick which sales channels list this product. Toggle a channel off to unlist it everywhere.
|
|
310
|
+
no_channels: No sales channels available. Create one in Settings → Sales channels.
|
|
311
|
+
published_at_label: Publish from
|
|
312
|
+
status_hidden: Hidden
|
|
313
|
+
status_live: Live
|
|
314
|
+
status_not_available: Not available
|
|
315
|
+
status_scheduled: Scheduled
|
|
316
|
+
title: Publishing
|
|
317
|
+
unpublished_at_label: Unpublish on
|
|
289
318
|
recommended_size: Recommended size
|
|
290
319
|
report_created: Your report is being generated. You will receive an email with a download link when it is ready!
|
|
291
320
|
reset_digital_link_download_limits: Reset download limits
|
data/config/routes.rb
CHANGED
|
@@ -226,6 +226,7 @@ Spree::Core::Engine.add_routes do
|
|
|
226
226
|
resources :payment_methods, except: :show
|
|
227
227
|
resources :shipping_methods, except: :show
|
|
228
228
|
resources :shipping_categories, except: :show
|
|
229
|
+
resources :channels, except: :show
|
|
229
230
|
resources :store_credit_categories
|
|
230
231
|
resources :tax_rates, except: :show
|
|
231
232
|
resources :tax_categories, except: :show
|
data/lib/spree/admin/engine.rb
CHANGED
|
@@ -9,6 +9,8 @@ module Spree
|
|
|
9
9
|
:admin_users_header_partials,
|
|
10
10
|
:body_end_partials,
|
|
11
11
|
:body_start_partials,
|
|
12
|
+
:channels_actions_partials,
|
|
13
|
+
:channels_header_partials,
|
|
12
14
|
:classifications_actions_partials,
|
|
13
15
|
:classifications_header_partials,
|
|
14
16
|
:coupon_codes_actions_partials,
|
|
@@ -17,6 +17,11 @@ module Spree
|
|
|
17
17
|
preference :legacy_sidebar_navigation, :boolean, default: false
|
|
18
18
|
|
|
19
19
|
preference :reports_line_items_limit, :integer, default: 1000
|
|
20
|
+
|
|
21
|
+
# Brute-force rate limiting for the admin dashboard auth endpoints (login / password reset).
|
|
22
|
+
preference :rate_limit_window, :integer, default: 60 # window in seconds
|
|
23
|
+
preference :rate_limit_login, :integer, default: 5 # per IP and per email
|
|
24
|
+
preference :rate_limit_password_reset, :integer, default: 3 # per IP and per email
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
end
|
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.
|
|
4
|
+
version: 5.5.0.rc2
|
|
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-
|
|
11
|
+
date: 2026-06-15 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.
|
|
19
|
+
version: 5.5.0.rc2
|
|
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.
|
|
26
|
+
version: 5.5.0.rc2
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: active_link_to
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -284,6 +284,7 @@ files:
|
|
|
284
284
|
- app/assets/tailwind/spree/admin/views/_dashboard.css
|
|
285
285
|
- app/assets/tailwind/spree/admin/views/_page-builder.css
|
|
286
286
|
- app/controllers/concerns/spree/admin/analytics_concern.rb
|
|
287
|
+
- app/controllers/concerns/spree/admin/auth_rate_limiting.rb
|
|
287
288
|
- app/controllers/concerns/spree/admin/breadcrumb_concern.rb
|
|
288
289
|
- app/controllers/concerns/spree/admin/bulk_operations_concern.rb
|
|
289
290
|
- app/controllers/concerns/spree/admin/order_breadcrumb_concern.rb
|
|
@@ -301,6 +302,7 @@ files:
|
|
|
301
302
|
- app/controllers/spree/admin/assets_controller.rb
|
|
302
303
|
- app/controllers/spree/admin/base_controller.rb
|
|
303
304
|
- app/controllers/spree/admin/bulk_operations_controller.rb
|
|
305
|
+
- app/controllers/spree/admin/channels_controller.rb
|
|
304
306
|
- app/controllers/spree/admin/checkouts_controller.rb
|
|
305
307
|
- app/controllers/spree/admin/classifications_controller.rb
|
|
306
308
|
- app/controllers/spree/admin/countries_controller.rb
|
|
@@ -392,6 +394,7 @@ files:
|
|
|
392
394
|
- app/helpers/spree/admin/base_helper.rb
|
|
393
395
|
- app/helpers/spree/admin/bulk_editor_helper.rb
|
|
394
396
|
- app/helpers/spree/admin/bulk_operations_helper.rb
|
|
397
|
+
- app/helpers/spree/admin/channels_helper.rb
|
|
395
398
|
- app/helpers/spree/admin/code_block_helper.rb
|
|
396
399
|
- app/helpers/spree/admin/customer_returns_helper.rb
|
|
397
400
|
- app/helpers/spree/admin/dialog_helper.rb
|
|
@@ -400,7 +403,6 @@ files:
|
|
|
400
403
|
- app/helpers/spree/admin/flash_helper.rb
|
|
401
404
|
- app/helpers/spree/admin/json_preview_helper.rb
|
|
402
405
|
- app/helpers/spree/admin/metafields_helper.rb
|
|
403
|
-
- app/helpers/spree/admin/modal_helper.rb
|
|
404
406
|
- app/helpers/spree/admin/navigation_helper.rb
|
|
405
407
|
- app/helpers/spree/admin/onboarding_helper.rb
|
|
406
408
|
- app/helpers/spree/admin/orders_filters_helper.rb
|
|
@@ -411,6 +413,7 @@ files:
|
|
|
411
413
|
- app/helpers/spree/admin/promotion_actions_helper.rb
|
|
412
414
|
- app/helpers/spree/admin/promotion_rules_helper.rb
|
|
413
415
|
- app/helpers/spree/admin/promotions_helper.rb
|
|
416
|
+
- app/helpers/spree/admin/publishing_helper.rb
|
|
414
417
|
- app/helpers/spree/admin/reimbursement_type_helper.rb
|
|
415
418
|
- app/helpers/spree/admin/shipment_helper.rb
|
|
416
419
|
- app/helpers/spree/admin/sortable_tree_helper.rb
|
|
@@ -456,6 +459,7 @@ files:
|
|
|
456
459
|
- app/javascript/spree/admin/controllers/page_builder_controller.js
|
|
457
460
|
- app/javascript/spree/admin/controllers/password_toggle_controller.js
|
|
458
461
|
- app/javascript/spree/admin/controllers/product_form_controller.js
|
|
462
|
+
- app/javascript/spree/admin/controllers/product_publishing_controller.js
|
|
459
463
|
- app/javascript/spree/admin/controllers/query_builder_controller.js
|
|
460
464
|
- app/javascript/spree/admin/controllers/range_input_controller.js
|
|
461
465
|
- app/javascript/spree/admin/controllers/replace_controller.js
|
|
@@ -546,6 +550,10 @@ files:
|
|
|
546
550
|
- app/views/spree/admin/bulk_operations/forms/_tag_picker.html.erb
|
|
547
551
|
- app/views/spree/admin/bulk_operations/forms/_taxon_picker.html.erb
|
|
548
552
|
- app/views/spree/admin/bulk_operations/new.html.erb
|
|
553
|
+
- app/views/spree/admin/channels/_form.html.erb
|
|
554
|
+
- app/views/spree/admin/channels/edit.html.erb
|
|
555
|
+
- app/views/spree/admin/channels/index.html.erb
|
|
556
|
+
- app/views/spree/admin/channels/new.html.erb
|
|
549
557
|
- app/views/spree/admin/checkouts/index.html.erb
|
|
550
558
|
- app/views/spree/admin/classifications/_classification.html.erb
|
|
551
559
|
- app/views/spree/admin/classifications/create.turbo_stream.erb
|
|
@@ -776,6 +784,7 @@ files:
|
|
|
776
784
|
- app/views/spree/admin/products/form/_base.html.erb
|
|
777
785
|
- app/views/spree/admin/products/form/_categorization.html.erb
|
|
778
786
|
- app/views/spree/admin/products/form/_inventory.html.erb
|
|
787
|
+
- app/views/spree/admin/products/form/_publishing.html.erb
|
|
779
788
|
- app/views/spree/admin/products/form/_shipping.html.erb
|
|
780
789
|
- app/views/spree/admin/products/form/_status.html.erb
|
|
781
790
|
- app/views/spree/admin/products/form/_tax.html.erb
|
|
@@ -795,17 +804,17 @@ files:
|
|
|
795
804
|
- app/views/spree/admin/promotion_actions/new.html.erb
|
|
796
805
|
- app/views/spree/admin/promotion_rules/_promotion_rule.html.erb
|
|
797
806
|
- app/views/spree/admin/promotion_rules/edit.html.erb
|
|
807
|
+
- app/views/spree/admin/promotion_rules/forms/_category.html.erb
|
|
798
808
|
- app/views/spree/admin/promotion_rules/forms/_country.html.erb
|
|
799
809
|
- app/views/spree/admin/promotion_rules/forms/_currency.html.erb
|
|
810
|
+
- app/views/spree/admin/promotion_rules/forms/_customer.html.erb
|
|
800
811
|
- app/views/spree/admin/promotion_rules/forms/_customer_group.html.erb
|
|
812
|
+
- app/views/spree/admin/promotion_rules/forms/_customer_logged_in.html.erb
|
|
801
813
|
- app/views/spree/admin/promotion_rules/forms/_first_order.html.erb
|
|
802
814
|
- app/views/spree/admin/promotion_rules/forms/_item_total.html.erb
|
|
803
815
|
- app/views/spree/admin/promotion_rules/forms/_one_use_per_user.html.erb
|
|
804
816
|
- app/views/spree/admin/promotion_rules/forms/_option_value.html.erb
|
|
805
817
|
- app/views/spree/admin/promotion_rules/forms/_product.html.erb
|
|
806
|
-
- app/views/spree/admin/promotion_rules/forms/_taxon.html.erb
|
|
807
|
-
- app/views/spree/admin/promotion_rules/forms/_user.html.erb
|
|
808
|
-
- app/views/spree/admin/promotion_rules/forms/_user_logged_in.html.erb
|
|
809
818
|
- app/views/spree/admin/promotion_rules/new.html.erb
|
|
810
819
|
- app/views/spree/admin/promotions/_actions.html.erb
|
|
811
820
|
- app/views/spree/admin/promotions/_form.html.erb
|
|
@@ -881,7 +890,6 @@ files:
|
|
|
881
890
|
- app/views/spree/admin/shared/_map.html.erb
|
|
882
891
|
- app/views/spree/admin/shared/_media_asset.html.erb
|
|
883
892
|
- app/views/spree/admin/shared/_media_form.html.erb
|
|
884
|
-
- app/views/spree/admin/shared/_modal.html.erb
|
|
885
893
|
- app/views/spree/admin/shared/_multi_product_picker.html.erb
|
|
886
894
|
- app/views/spree/admin/shared/_new_item_dropdown.html.erb
|
|
887
895
|
- app/views/spree/admin/shared/_new_resource.html.erb
|
|
@@ -1164,9 +1172,9 @@ licenses:
|
|
|
1164
1172
|
- BSD-3-Clause
|
|
1165
1173
|
metadata:
|
|
1166
1174
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
1167
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.
|
|
1175
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.0.rc2
|
|
1168
1176
|
documentation_uri: https://docs.spreecommerce.org/
|
|
1169
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.
|
|
1177
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.5.0.rc2
|
|
1170
1178
|
post_install_message:
|
|
1171
1179
|
rdoc_options: []
|
|
1172
1180
|
require_paths:
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
module Spree
|
|
2
|
-
module Admin
|
|
3
|
-
module ModalHelper
|
|
4
|
-
# render a header for the modal
|
|
5
|
-
# @param title [String, Proc] the title of the modal
|
|
6
|
-
# @return [String]
|
|
7
|
-
def modal_header(title)
|
|
8
|
-
Spree::Deprecation.warn('Bootstrap modals are deprecated and will be removed in Spree 5.5. Please use native dialogs with `dialog_header` helper.')
|
|
9
|
-
|
|
10
|
-
title = capture(&title) if block_given?
|
|
11
|
-
content_tag(:div, class: 'modal-header') do
|
|
12
|
-
content_tag(:h5, title, class: 'modal-title') + modal_close_button
|
|
13
|
-
end.html_safe
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# render a close button for the modal
|
|
17
|
-
# @return [String]
|
|
18
|
-
def modal_close_button
|
|
19
|
-
button_tag('', type: 'button', class: 'btn-close', data: { dismiss: 'modal', aria_label: Spree.t(:close) }).html_safe
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# render a discard button for the modal
|
|
23
|
-
# @return [String]
|
|
24
|
-
def modal_discard_button
|
|
25
|
-
button_tag(type: 'button', class: 'btn btn-light mr-auto', data: { dismiss: 'modal' }) do
|
|
26
|
-
Spree.t('actions.discard')
|
|
27
|
-
end.html_safe
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|