spree_admin 5.5.0 → 5.5.2

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: b30149d90f37c53c387243e7e49e514b7258f1e2177b051acfb9ffb8c3287dd3
4
- data.tar.gz: 60c5d27fc0266c617c05eff1a2d49afcfcde6d46c0af8ae08c245729cb827a0e
3
+ metadata.gz: fe7bbf921b79a573c2d091927442a90d3f7305fa2c8024b34c3a75ee9a0d3b26
4
+ data.tar.gz: 72fd353cee9a2dfba89b70cbeb09e1e2260b4876d468d7d34c0435aeeca2ec3a
5
5
  SHA512:
6
- metadata.gz: 8632ab90b01c2fb9ed984db3d33460d788e7c2560b863409a61cc5ed0a8026c4a89eab056d451d2102c52775d8b8d7b3465559919ad660f1c92c44534129a678
7
- data.tar.gz: d047099cb153ae0f0b961f30ff54144f2683c2572cefca8d5c0afc6ccafa626201647599e0e6d3544ab7149eefc7f332c060bcaf40e1f47f3df809df4d89e852
6
+ metadata.gz: d5c8f4935b961a62800280b15ea76248e9f3c132ca7aa02ef33952953bbe65efec1595f87f028aa4d41ea5446b7feb841029e9b761ac9b093e1af4cda4e26fd0
7
+ data.tar.gz: a26a759b7ed7f13c37eaf09aa9a18e5edacc46d0b6fc4fd23bc8c88a5782f8ae4a02ad19c391e90f5d474157c7f21a716376f249aa3c9bd402377b23caa1326e
@@ -168,7 +168,7 @@
168
168
  }
169
169
 
170
170
  .dialog-footer {
171
- @apply sticky bottom-0 p-4 flex gap-2 items-center justify-between border-t border-gray-100 bg-gray-25;
171
+ @apply shrink-0 p-4 flex gap-2 items-center justify-between border-t border-gray-100 bg-gray-25;
172
172
  border-radius: 0 0 calc(var(--radius-2xl) - 1px) calc(var(--radius-2xl) - 1px);
173
173
  }
174
174
 
@@ -0,0 +1,7 @@
1
+ <div class="form-group">
2
+ <%= f.label :preferred_channel_ids, Spree.t(:channels) %>
3
+ <%= f.select :preferred_channel_ids,
4
+ current_store.channels.order(:name).pluck(:name, :id),
5
+ { selected: f.object.preferred_channel_ids },
6
+ { multiple: true, data: { controller: 'autocomplete-select' } } %>
7
+ </div>
@@ -18,6 +18,7 @@
18
18
  tz_help = Spree.t('admin.datetime_field_timezone_help', zone: store_timezone.name)
19
19
  store_channels = current_store.channels.order(:name).to_a
20
20
  publications_by_channel = @product.product_publications.includes(:channel).index_by(&:channel_id)
21
+ can_manage_publications = can?(:manage, Spree::ProductPublication)
21
22
 
22
23
  # Build one record per store channel: the existing publication when
23
24
  # attached, or a fresh ProductPublication when not. Stable channel-keyed
@@ -33,7 +34,7 @@
33
34
  <div class="card mb-6" data-controller="product-publishing">
34
35
  <div class="card-header d-flex align-items-center justify-content-between">
35
36
  <h6 class="card-title mb-0"><%= Spree.t('admin.publishing.title') %></h6>
36
- <% if store_channels.any? %>
37
+ <% if store_channels.any? && can_manage_publications %>
37
38
  <button type="button"
38
39
  class="btn btn-sm btn-outline-secondary"
39
40
  data-action="product-publishing#toggleManage">
@@ -47,6 +48,7 @@
47
48
  <p class="text-xs text-muted mb-0"><%= Spree.t('admin.publishing.no_channels') %></p>
48
49
  <% else %>
49
50
  <%# ---- Manage panel (hidden by default) ---- %>
51
+ <% if can_manage_publications %>
50
52
  <div class="d-none flex flex-col gap-2 mb-3" data-product-publishing-target="manage">
51
53
  <p class="text-xs text-muted mb-1"><%= Spree.t('admin.publishing.manage_description') %></p>
52
54
  <% channel_rows.each_with_index do |(channel, publication), index| %>
@@ -73,6 +75,7 @@
73
75
  <% end %>
74
76
  <% end %>
75
77
  </div>
78
+ <% end %>
76
79
 
77
80
  <%# ---- Per-publication scheduling rows ---- %>
78
81
  <% if publications_by_channel.empty? %>
@@ -80,43 +83,57 @@
80
83
  <% else %>
81
84
  <% channel_rows.each_with_index do |(channel, publication), index| %>
82
85
  <% 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) %>
86
+ <% if can_manage_publications %>
87
+ <%= f.fields_for :legacy_product_publications, publication, child_index: index do |pf| %>
88
+ <details class="publication-row group rounded -mx-2 px-2 hover:bg-gray-50">
89
+ <summary class="flex items-start justify-between gap-3 py-2 cursor-pointer" style="list-style: none;">
90
+ <div class="flex min-w-0 flex-1 flex-col gap-0.5">
91
+ <div class="flex items-center gap-2">
92
+ <span class="truncate text-sm font-medium"><%= channel.name %></span>
93
+ <%= publication_status_badge(@product.status, publication) %>
94
+ </div>
95
+ <span class="text-xs text-muted">
96
+ <%= publication_caption(@product.status, publication, current_store) %>
97
+ </span>
90
98
  </div>
91
- <span class="text-xs text-muted">
92
- <%= publication_caption(@product.status, publication, current_store) %>
99
+ <span class="shrink-0 mt-1 opacity-0 transition-opacity group-hover:opacity-100 text-muted">
100
+ <%= icon('pencil', class: 'h-3.5 w-3.5') %>
93
101
  </span>
102
+ </summary>
103
+ <div class="mt-2 ps-2 pb-2 flex flex-col gap-3">
104
+ <div>
105
+ <%= pf.label :published_at,
106
+ Spree.t('admin.publishing.published_at_label'),
107
+ class: 'form-label text-xs mb-1' %>
108
+ <%= pf.datetime_field :published_at,
109
+ value: to_store_local.call(publication.published_at),
110
+ class: 'form-control form-control-sm' %>
111
+ <small class="text-muted text-xs"><%= tz_help %></small>
112
+ </div>
113
+ <div>
114
+ <%= pf.label :unpublished_at,
115
+ Spree.t('admin.publishing.unpublished_at_label'),
116
+ class: 'form-label text-xs mb-1' %>
117
+ <%= pf.datetime_field :unpublished_at,
118
+ value: to_store_local.call(publication.unpublished_at),
119
+ class: 'form-control form-control-sm' %>
120
+ <small class="text-muted text-xs"><%= tz_help %></small>
121
+ </div>
94
122
  </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>
123
+ </details>
124
+ <% end %>
125
+ <% else %>
126
+ <div class="publication-row -mx-2 px-2">
127
+ <div class="flex min-w-0 flex-1 flex-col gap-0.5 py-2">
128
+ <div class="flex items-center gap-2">
129
+ <span class="truncate text-sm font-medium"><%= channel.name %></span>
130
+ <%= publication_status_badge(@product.status, publication) %>
117
131
  </div>
132
+ <span class="text-xs text-muted">
133
+ <%= publication_caption(@product.status, publication, current_store) %>
134
+ </span>
118
135
  </div>
119
- </details>
136
+ </div>
120
137
  <% end %>
121
138
  <% end %>
122
139
  <% end %>
@@ -21,10 +21,10 @@
21
21
 
22
22
  <%= invite_vendor_button(class: 'dropdown-item') if defined?(invite_vendor_button) %>
23
23
 
24
- <% if can?(:create, Spree::Store) && Spree.root_domain.present? %>
24
+ <% if can?(:create, Spree::Store) && Spree.root_domain.present? && spree.respond_to?(:new_admin_store_path) %>
25
25
  <div class="dropdown-divider"></div>
26
26
 
27
27
  <%= link_to_with_icon 'building-store', Spree.t(:new_store), spree.new_admin_store_path, class: 'dropdown-item' %>
28
28
  <% end %>
29
29
  <% end %>
30
- <% end %>
30
+ <% end %>
@@ -47,20 +47,42 @@ module Spree
47
47
  end
48
48
  end
49
49
 
50
+ # [relative dir, glob] pairs Tailwind scans for utility classes.
51
+ SOURCE_PATHS = [
52
+ ["app/views/spree/admin", "**/*.erb"],
53
+ ["app/helpers/spree/admin", "**/*.rb"],
54
+ ["app/javascript/spree/admin", "**/*.js"]
55
+ ].freeze
56
+
50
57
  def engine_sources(engine)
51
58
  root = engine.root.to_s
52
- source_paths = [
53
- ["app/views/spree/admin", "**/*.erb"],
54
- ["app/helpers/spree/admin", "**/*.rb"],
55
- ["app/javascript/spree/admin", "**/*.js"]
56
- ]
57
59
 
58
- source_paths.filter_map do |dir, pattern|
60
+ SOURCE_PATHS.filter_map do |dir, pattern|
59
61
  full_dir = File.join(root, dir)
60
62
  %(@source "#{full_dir}/#{pattern}";) if File.directory?(full_dir)
61
63
  end
62
64
  end
63
65
 
66
+ # Every template/CSS glob that affects the compiled admin CSS, across all
67
+ # Spree engines plus the host app. Used by watch mode to poll for changes
68
+ # (OS file-system events don't cross a Docker bind mount from a macOS host).
69
+ #
70
+ # @return [Array<String>] absolute Dir.glob patterns
71
+ def source_globs
72
+ roots = spree_engines.map { |engine| engine.root.to_s }
73
+ roots << Rails.root.to_s
74
+
75
+ template_globs = roots.product(SOURCE_PATHS).map do |root, (dir, pattern)|
76
+ File.join(root, dir, pattern)
77
+ end
78
+
79
+ css_globs = [engine_css_path.to_s, input_path.dirname.to_s].map do |dir|
80
+ File.join(dir, "**", "*.css")
81
+ end
82
+
83
+ (template_globs + css_globs).uniq
84
+ end
85
+
64
86
  def write_resolved_css
65
87
  FileUtils.mkdir_p(resolved_input_path.dirname)
66
88
  File.write(resolved_input_path, resolved_input_css)
@@ -29,54 +29,57 @@ namespace :spree do
29
29
  task watch: :environment do
30
30
  require "tailwindcss/ruby"
31
31
 
32
- begin
33
- require "listen"
34
- rescue LoadError
35
- puts "ERROR: The 'listen' gem is required for watch mode."
36
- puts "Add it to your Gemfile in the development group:"
37
- puts ""
38
- puts " group :development do"
39
- puts " gem 'listen', '>= 3.0'"
40
- puts " end"
41
- puts ""
42
- exit 1
43
- end
44
-
45
32
  output_path = Spree::Admin::TailwindHelper.output_path
46
33
  FileUtils.mkdir_p(output_path.dirname)
47
34
 
48
- # Initial write of resolved CSS
49
- resolved_path = Spree::Admin::TailwindHelper.write_resolved_css
35
+ resolved_path = Spree::Admin::TailwindHelper.resolved_input_path
36
+
37
+ # One-shot Tailwind build. Polling drives recompilation instead of
38
+ # Tailwind's own --watch: its file watching (like the `listen` gem's)
39
+ # relies on OS file-system events, which don't cross a Docker bind mount
40
+ # from a macOS host, so a host-side template edit would never rebuild.
41
+ build = lambda do
42
+ Spree::Admin::TailwindHelper.write_resolved_css
43
+ system(Tailwindcss::Ruby.executable, "-i", resolved_path.to_s, "-o", output_path.to_s)
44
+ # system doesn't raise on a non-zero exit, so surface build failures
45
+ # (invalid CSS, missing binary) — otherwise a broken rebuild is silent.
46
+ warn "[tailwind watch] build failed (exit #{$?.exitstatus})" unless $?.success?
47
+ end
48
+
49
+ source_mtimes = lambda do
50
+ Spree::Admin::TailwindHelper.source_globs.each_with_object({}) do |glob, acc|
51
+ Dir.glob(glob).each do |file|
52
+ acc[file] = File.mtime(file) rescue next
53
+ end
54
+ end
55
+ end
56
+
50
57
  puts "Watching Spree Admin Tailwind CSS for changes..."
51
58
  puts " Host input: #{Spree::Admin::TailwindHelper.input_path}"
52
59
  puts " Engine CSS: #{Spree::Admin::TailwindHelper.engine_css_path}"
53
60
  puts " Resolved: #{resolved_path}"
54
61
  puts " Output: #{output_path}"
55
62
 
56
- # Watch paths for CSS source changes
57
- watch_paths = [
58
- Spree::Admin::TailwindHelper.engine_css_path.to_s, # Engine CSS files
59
- Spree::Admin::TailwindHelper.input_path.dirname.to_s # Host app CSS files
60
- ].select { |p| File.directory?(p) }
61
-
62
- # Set up listener to regenerate resolved CSS when source files change
63
- listener = Listen.to(*watch_paths, only: /\.css$/) do |modified, added, removed|
64
- changed = (modified + added + removed).map { |f| File.basename(f) }.join(", ")
65
- puts "\n[#{Time.now.strftime('%H:%M:%S')}] CSS changed: #{changed}"
66
- puts " Regenerating resolved CSS..."
67
- Spree::Admin::TailwindHelper.write_resolved_css
63
+ build.call
64
+ previous = source_mtimes.call
65
+
66
+ loop do
67
+ sleep 1
68
+ begin
69
+ current = source_mtimes.call
70
+ next if current == previous
71
+
72
+ changed = (current.keys | previous.keys).select { |f| current[f] != previous[f] }
73
+ .map { |f| File.basename(f) }.uniq.first(5).join(", ")
74
+ previous = current
75
+ puts "\n[#{Time.now.strftime('%H:%M:%S')}] Sources changed: #{changed} — rebuilding..."
76
+ build.call
77
+ rescue => e
78
+ # A transient read/build error (e.g. a source file caught mid-save)
79
+ # must not kill the watch loop — log and keep polling.
80
+ warn "[tailwind watch] rebuild failed: #{e.class}: #{e.message}"
81
+ end
68
82
  end
69
- listener.start
70
-
71
- # Run Tailwind in watch mode (this blocks)
72
- command = [
73
- Tailwindcss::Ruby.executable,
74
- "-i", resolved_path.to_s,
75
- "-o", output_path.to_s,
76
- "--watch"
77
- ]
78
-
79
- system(*command)
80
83
  end
81
84
  end
82
85
  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.5.0
4
+ version: 5.5.2
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-06-18 00:00:00.000000000 Z
11
+ date: 2026-07-06 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.5.0
19
+ version: 5.5.2
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.5.0
26
+ version: 5.5.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: active_link_to
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -768,6 +768,7 @@ files:
768
768
  - app/views/spree/admin/price_lists/update.turbo_stream.erb
769
769
  - app/views/spree/admin/price_rules/_price_rule.html.erb
770
770
  - app/views/spree/admin/price_rules/edit.html.erb
771
+ - app/views/spree/admin/price_rules/forms/_channel_rule.html.erb
771
772
  - app/views/spree/admin/price_rules/forms/_customer_group_rule.html.erb
772
773
  - app/views/spree/admin/price_rules/forms/_market_rule.html.erb
773
774
  - app/views/spree/admin/price_rules/forms/_user_rule.html.erb
@@ -1172,9 +1173,9 @@ licenses:
1172
1173
  - BSD-3-Clause
1173
1174
  metadata:
1174
1175
  bug_tracker_uri: https://github.com/spree/spree/issues
1175
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.0
1176
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.2
1176
1177
  documentation_uri: https://docs.spreecommerce.org/
1177
- source_code_uri: https://github.com/spree/spree/tree/v5.5.0
1178
+ source_code_uri: https://github.com/spree/spree/tree/v5.5.2
1178
1179
  post_install_message:
1179
1180
  rdoc_options: []
1180
1181
  require_paths: