spree_admin 5.1.0.beta3 → 5.1.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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/spree/admin/components/_badges.scss +4 -0
  3. data/app/assets/stylesheets/spree/admin/components/_buttons.scss +13 -2
  4. data/app/assets/stylesheets/spree/admin/components/_filters.scss +8 -1
  5. data/app/assets/stylesheets/spree/admin/components/_main.scss +14 -2
  6. data/app/assets/stylesheets/spree/admin/components/_media_form.scss +1 -1
  7. data/app/assets/stylesheets/spree/admin/components/_modals.scss +3 -1
  8. data/app/assets/stylesheets/spree/admin/components/_navbar.scss +1 -1
  9. data/app/assets/stylesheets/spree/admin/components/_tables.scss +6 -10
  10. data/app/assets/stylesheets/spree/admin/components/_variants_form.scss +1 -1
  11. data/app/assets/stylesheets/spree/admin/global/_variables.scss +13 -8
  12. data/app/assets/stylesheets/spree/admin/shared/_base.scss +5 -0
  13. data/app/controllers/concerns/spree/admin/order_breadcrumb_concern.rb +12 -0
  14. data/app/controllers/spree/admin/gift_card_batches_controller.rb +23 -0
  15. data/app/controllers/spree/admin/gift_cards_controller.rb +94 -0
  16. data/app/controllers/spree/admin/orders_controller.rb +20 -5
  17. data/app/controllers/spree/admin/payments_controller.rb +3 -0
  18. data/app/controllers/spree/admin/products_controller.rb +3 -3
  19. data/app/controllers/spree/admin/refunds_controller.rb +4 -0
  20. data/app/controllers/spree/admin/reimbursements_controller.rb +5 -1
  21. data/app/controllers/spree/admin/resource_controller.rb +12 -1
  22. data/app/controllers/spree/admin/search_controller.rb +22 -0
  23. data/app/controllers/spree/admin/store_credits_controller.rb +8 -5
  24. data/app/helpers/spree/admin/base_helper.rb +55 -0
  25. data/app/helpers/spree/admin/navigation_helper.rb +2 -2
  26. data/app/helpers/spree/admin/payments_helper.rb +0 -5
  27. data/app/helpers/spree/admin/products_helper.rb +1 -1
  28. data/app/helpers/spree/admin/promotion_rules_helper.rb +13 -1
  29. data/app/helpers/spree/admin/shipment_helper.rb +2 -0
  30. data/app/javascript/spree/admin/application.js +2 -0
  31. data/app/javascript/spree/admin/controllers/active_storage_upload_controller.js +2 -11
  32. data/app/javascript/spree/admin/controllers/multi_tom_select_controller.js +3 -2
  33. data/app/javascript/spree/admin/controllers/return_items_controller.js +6 -1
  34. data/app/javascript/spree/admin/controllers/sticky_controller.js +24 -0
  35. data/app/javascript/spree/admin/controllers/variants_form_controller.js +23 -17
  36. data/app/javascript/spree/admin/helpers/uppy_active_storage.js +15 -3
  37. data/app/views/active_storage/_upload_form.html.erb +3 -3
  38. data/app/views/spree/admin/coupon_codes/_coupon_code.html.erb +2 -6
  39. data/app/views/spree/admin/coupon_codes/index.csv.erb +1 -1
  40. data/app/views/spree/admin/custom_domains/edit.html.erb +2 -3
  41. data/app/views/spree/admin/custom_domains/new.html.erb +2 -3
  42. data/app/views/spree/admin/dashboard/_setup_progress.html.erb +2 -3
  43. data/app/views/spree/admin/dashboard/_store_preview.html.erb +3 -9
  44. data/app/views/spree/admin/gift_card_batches/_form.html.erb +30 -0
  45. data/app/views/spree/admin/gift_card_batches/new.html.erb +15 -0
  46. data/app/views/spree/admin/gift_cards/_filters.html.erb +43 -0
  47. data/app/views/spree/admin/gift_cards/_form.html.erb +30 -0
  48. data/app/views/spree/admin/gift_cards/_gift_card.html.erb +28 -0
  49. data/app/views/spree/admin/gift_cards/_list.html.erb +24 -0
  50. data/app/views/spree/admin/gift_cards/_table_filter_dropdown.html.erb +25 -0
  51. data/app/views/spree/admin/gift_cards/edit.html.erb +20 -0
  52. data/app/views/spree/admin/gift_cards/index.csv.erb +22 -0
  53. data/app/views/spree/admin/gift_cards/index.html.erb +41 -0
  54. data/app/views/spree/admin/gift_cards/new.html.erb +30 -0
  55. data/app/views/spree/admin/gift_cards/show.html.erb +199 -0
  56. data/app/views/spree/admin/integrations/new.html.erb +7 -4
  57. data/app/views/spree/admin/oauth_applications/edit.html.erb +2 -3
  58. data/app/views/spree/admin/oauth_applications/new.html.erb +2 -3
  59. data/app/views/spree/admin/orders/_customer.html.erb +5 -2
  60. data/app/views/spree/admin/orders/_payments.html.erb +1 -1
  61. data/app/views/spree/admin/orders/_return_authorizations.html.erb +3 -1
  62. data/app/views/spree/admin/orders/_shipment.html.erb +1 -1
  63. data/app/views/spree/admin/orders/_shipments.html.erb +1 -1
  64. data/app/views/spree/admin/orders/return_authorizations/_form.html.erb +13 -11
  65. data/app/views/spree/admin/page_sections/forms/_featured_posts.html.erb +0 -5
  66. data/app/views/spree/admin/pages/_form.html.erb +20 -26
  67. data/app/views/spree/admin/pages/new.html.erb +10 -4
  68. data/app/views/spree/admin/payments/_payment.html.erb +14 -9
  69. data/app/views/spree/admin/payments/new.html.erb +1 -1
  70. data/app/views/spree/admin/payments/source_forms/_gateway.html.erb +1 -1
  71. data/app/views/spree/admin/post_categories/edit.html.erb +2 -3
  72. data/app/views/spree/admin/post_categories/new.html.erb +2 -3
  73. data/app/views/spree/admin/preferences/_password_field.html.erb +2 -2
  74. data/app/views/spree/admin/products/edit.html.erb +2 -2
  75. data/app/views/spree/admin/promotion_actions/_promotion_action.html.erb +1 -1
  76. data/app/views/spree/admin/promotion_actions/edit.html.erb +2 -2
  77. data/app/views/spree/admin/promotion_rules/_promotion_rule.html.erb +26 -9
  78. data/app/views/spree/admin/promotion_rules/edit.html.erb +2 -2
  79. data/app/views/spree/admin/promotion_rules/forms/_option_value.html.erb +1 -1
  80. data/app/views/spree/admin/promotions/_actions.html.erb +2 -2
  81. data/app/views/spree/admin/promotions/_rules.html.erb +2 -2
  82. data/app/views/spree/admin/promotions/_sidebar.html.erb +5 -13
  83. data/app/views/spree/admin/properties/edit.html.erb +9 -5
  84. data/app/views/spree/admin/properties/new.html.erb +9 -5
  85. data/app/views/spree/admin/refunds/new.html.erb +5 -2
  86. data/app/views/spree/admin/reimbursement_types/edit.html.erb +2 -3
  87. data/app/views/spree/admin/reimbursement_types/new.html.erb +2 -4
  88. data/app/views/spree/admin/shared/_content_header.html.erb +9 -4
  89. data/app/views/spree/admin/shared/_edit_resource_links.html.erb +3 -1
  90. data/app/views/spree/admin/shared/_header.html.erb +2 -2
  91. data/app/views/spree/admin/shared/_index_table_options.html.erb +1 -1
  92. data/app/views/spree/admin/shared/_preferences.html.erb +1 -0
  93. data/app/views/spree/admin/shared/_sidebar.html.erb +1 -1
  94. data/app/views/spree/admin/shared/_user.html.erb +2 -7
  95. data/app/views/spree/admin/shared/named_types/_edit.html.erb +2 -3
  96. data/app/views/spree/admin/shared/named_types/_new.html.erb +2 -3
  97. data/app/views/spree/admin/shared/sidebar/_orders_nav.html.erb +1 -1
  98. data/app/views/spree/admin/shared/sortable_tree/_taxonomy.html.erb +1 -1
  99. data/app/views/spree/admin/shipping_categories/edit.html.erb +7 -4
  100. data/app/views/spree/admin/shipping_categories/new.html.erb +7 -6
  101. data/app/views/spree/admin/shipping_methods/form/_display.html.erb +23 -1
  102. data/app/views/spree/admin/stock_locations/_form.html.erb +35 -51
  103. data/app/views/spree/admin/stock_locations/edit.html.erb +8 -3
  104. data/app/views/spree/admin/stock_locations/new.html.erb +8 -5
  105. data/app/views/spree/admin/store_credit_events/_store_credit_event.html.erb +29 -0
  106. data/app/views/spree/admin/store_credits/_form.html.erb +13 -21
  107. data/app/views/spree/admin/store_credits/_list.html.erb +4 -24
  108. data/app/views/spree/admin/store_credits/_store_credit.html.erb +11 -0
  109. data/app/views/spree/admin/store_credits/edit.html.erb +16 -31
  110. data/app/views/spree/admin/store_credits/new.html.erb +14 -12
  111. data/app/views/spree/admin/store_credits/show.html.erb +145 -0
  112. data/app/views/spree/admin/stores/form/_basic.html.erb +26 -10
  113. data/app/views/spree/admin/tax_categories/_form.html.erb +1 -1
  114. data/app/views/spree/admin/tax_categories/edit.html.erb +7 -4
  115. data/app/views/spree/admin/tax_categories/new.html.erb +7 -4
  116. data/app/views/spree/admin/tax_rates/_form.html.erb +34 -32
  117. data/app/views/spree/admin/tax_rates/edit.html.erb +7 -4
  118. data/app/views/spree/admin/tax_rates/new.html.erb +7 -7
  119. data/app/views/spree/admin/taxonomies/edit.html.erb +2 -4
  120. data/app/views/spree/admin/taxonomies/new.html.erb +2 -2
  121. data/app/views/spree/admin/translations/edit.html.erb +2 -2
  122. data/app/views/spree/admin/translations/stores/_form.html.erb +2 -0
  123. data/app/views/spree/admin/users/_details.html.erb +1 -0
  124. data/app/views/spree/admin/users/index.html.erb +1 -1
  125. data/app/views/spree/admin/users/new.html.erb +9 -5
  126. data/app/views/spree/admin/users/show.html.erb +3 -3
  127. data/app/views/spree/admin/variants/form/_inventory.html.erb +1 -1
  128. data/app/views/spree/admin/zones/_form.html.erb +36 -40
  129. data/app/views/spree/admin/zones/edit.html.erb +7 -5
  130. data/app/views/spree/admin/zones/new.html.erb +7 -4
  131. data/config/locales/en.yml +12 -1
  132. data/config/routes.rb +8 -2
  133. metadata +28 -8
@@ -243,6 +243,61 @@ module Spree
243
243
  content_tag(:span, ' *', class: 'required font-weight-bold text-danger')
244
244
  end
245
245
 
246
+ # renders a clipboard button
247
+ # @param options [Hash] the options for the button
248
+ # @option options [String] :class the CSS class(es) of the button
249
+ # @option options [Hash] :data the data attributes for the button
250
+ # @option options [String] :title the title of the button
251
+ # @return [String] the button
252
+ def clipboard_button(options = {})
253
+ options[:class] ||= 'btn btn-clipboard with-tip'
254
+ options[:type] ||= 'button'
255
+ options[:data] ||= {}
256
+ options[:data][:action] = 'clipboard#copy'
257
+ options[:data][:clipboard_target] = 'button'
258
+ options[:data][:title] = Spree.t('admin.copy_to_clipboard')
259
+ options[:aria_label] ||= Spree.t('admin.copy_to_clipboard') # screen-reader label
260
+
261
+ content_tag(:button, options) do
262
+ icon('copy', class: 'mr-0 font-size-sm')
263
+ end
264
+ end
265
+
266
+ # renders a clipboard component
267
+ # @param text [String] the text to copy
268
+ # @param options [Hash] the options for the component
269
+ # @option options [String] :class the CSS class(es) of the component
270
+ # @option options [Hash] :data the data attributes for the component
271
+ # @option options [String] :title the title of the component
272
+ # @return [String] the component
273
+ def clipboard_component(text, options = {})
274
+ options[:data] ||= {}
275
+ options[:data][:controller] = 'clipboard'
276
+ options[:data][:clipboard_success_content_value] ||= raw(icon('check', class: 'mr-0 font-size-sm'))
277
+
278
+ content_tag(:span, data: options[:data]) do
279
+ hidden_field_tag(:clipboard_source, text, data: { clipboard_target: 'source' }) +
280
+ clipboard_button
281
+ end
282
+ end
283
+
284
+ # renders a progress bar component
285
+ # @param options [Hash] the options for the component
286
+ # @param value [Integer] the value of the progress bar
287
+ # @option options [Integer] :min the minimum value of the progress bar
288
+ # @option options [Integer] :max the maximum value of the progress bar
289
+ # @return [String] the component
290
+ def progress_bar_component(value, options = {})
291
+ min = options[:min] || 0
292
+ max = options[:max] || 100
293
+ percentage = (value.to_f / max * 100).round
294
+
295
+ content_tag(:div, class: 'progress') do
296
+ content_tag(:div, { class: 'progress-bar', role: 'progressbar', style: "width: #{percentage}%", aria: { valuenow: value, valuemin: min, valuemax: max } }) do
297
+ end
298
+ end
299
+ end
300
+
246
301
  # returns the allowed file types for upload, according to the active storage configuration
247
302
  # @return [Array<String>] the allowed file types for upload, eg. ['image/png', 'image/jpeg', 'image/gif', 'image/webp']
248
303
  def allowed_file_types_for_upload
@@ -231,7 +231,7 @@ module Spree
231
231
  link_to url, opts, &block
232
232
  else
233
233
  link_to url, opts do
234
- (label + icon('external-link', class: 'ml-1 mr-0 small')).html_safe
234
+ (label + icon('external-link', class: 'ml-1 mr-0 small opacity-50')).html_safe
235
235
  end
236
236
  end
237
237
  end
@@ -263,7 +263,7 @@ module Spree
263
263
  # @param css [String] the css class of the help bubble
264
264
  # @return [String] the help bubble with the icon
265
265
  def help_bubble(text = '', placement = 'bottom', css: nil)
266
- css ||= 'text-muted opacity-75 cursor-help'
266
+ css ||= 'text-muted opacity-75 cursor-default'
267
267
  content_tag :small, icon('info-square-rounded', class: css), data: { placement: placement }, class: "with-tip #{css}", title: text
268
268
  end
269
269
 
@@ -11,11 +11,6 @@ module Spree
11
11
  end
12
12
  end
13
13
 
14
- def payment_method_icon_tag(payment_method, opts = {})
15
- image_tag "payment_icons/#{payment_method}.svg", opts
16
- rescue Sprockets::Rails::Helper::AssetNotFound
17
- end
18
-
19
14
  def available_payment_methods
20
15
  @available_payment_methods ||= Spree::PaymentMethod.providers.map { |provider| provider.name.constantize.new }.delete_if { |payment_method| !payment_method.show_in_admin? || current_store.payment_methods.pluck(:type).include?(payment_method.type) }.sort_by(&:name)
21
16
  end
@@ -124,7 +124,7 @@ module Spree
124
124
  end
125
125
 
126
126
  def sorted_product_properties(product)
127
- product.product_properties.sort_by { |product_property| product_property.property.position }
127
+ product.product_properties.includes(:property).sort_by { |product_property| product_property.property.position }
128
128
  end
129
129
  end
130
130
  end
@@ -10,7 +10,19 @@ module Spree
10
10
  eligible_values = promotion_rule.preferred_eligible_values || []
11
11
  return [] if eligible_values.empty?
12
12
 
13
- Spree::OptionValueVariant.where(id: eligible_values).to_tom_select_json
13
+ Spree::OptionValue.includes(:option_type).where(id: eligible_values).map do |ov|
14
+ {
15
+ id: ov.id,
16
+ name: ov.display_presentation
17
+ }
18
+ end
19
+ end
20
+
21
+ # Returns the promotion rule option values
22
+ # @param value_ids [Array<Integer>]
23
+ # @return [Array<String>]
24
+ def promotion_rule_option_values(value_ids)
25
+ Spree::OptionValue.includes(:option_type).where(id: value_ids).map(&:display_presentation)
14
26
  end
15
27
  end
16
28
  end
@@ -1,5 +1,7 @@
1
1
  module Spree::Admin
2
2
  module ShipmentHelper
3
+ include Spree::ShipmentHelper
4
+
3
5
  def can_ship?(shipment)
4
6
  can?(:update, shipment) && shipment.shippable?
5
7
  end
@@ -73,6 +73,7 @@ import SectionFormController from 'spree/admin/controllers/section_form_controll
73
73
  import SelectController from 'spree/admin/controllers/select_controller'
74
74
  import SeoFormController from 'spree/admin/controllers/seo_form_controller'
75
75
  import SlugFormController from 'spree/admin/controllers/slug_form_controller'
76
+ import StickyController from 'spree/admin/controllers/sticky_controller'
76
77
  import SortableTree from 'spree/admin/controllers/sortable_tree_controller'
77
78
  import StockTransferController from 'spree/admin/controllers/stock_transfer_controller'
78
79
  import StoreFormController from 'spree/admin/controllers/store_form_controller'
@@ -124,6 +125,7 @@ application.register('section-form', SectionFormController)
124
125
  application.register('select', SelectController)
125
126
  application.register('seo-form', SeoFormController)
126
127
  application.register('slug-form', SlugFormController)
128
+ application.register('sticky', StickyController)
127
129
  application.register('sortable', Sortable)
128
130
  application.register('sortable-tree', SortableTree)
129
131
  application.register('stock-transfer', StockTransferController)
@@ -29,7 +29,8 @@ export default class extends Controller {
29
29
  })
30
30
 
31
31
  this.uppy.use(ActiveStorageUpload, {
32
- directUploadUrl: document.querySelector("meta[name='direct-upload-url']").getAttribute('content')
32
+ directUploadUrl: document.querySelector("meta[name='direct-upload-url']").getAttribute('content'),
33
+ crop: this.cropValue
33
34
  })
34
35
 
35
36
  let dashboardOptions = {}
@@ -55,16 +56,6 @@ export default class extends Controller {
55
56
 
56
57
  this.uppy.on('file-editor:complete', (updatedFile) => {
57
58
  console.log('File editing complete:', updatedFile)
58
- // Remove the old file from Uppy's file list
59
- this.uppy.removeFile(updatedFile.id)
60
- // Add the updated file to Uppy
61
- this.uppy.addFile({
62
- name: updatedFile.name,
63
- type: updatedFile.type,
64
- data: updatedFile.data,
65
- source: 'local',
66
- isRemote: false
67
- })
68
59
 
69
60
  this.handleUI(updatedFile)
70
61
 
@@ -26,8 +26,9 @@ export default class extends Controller {
26
26
  values() {
27
27
  return this.selectTargets
28
28
  .map((selectTarget) => selectTarget.querySelector('select'))
29
- .map((select) => select.options[select.selectedIndex].text)
30
- .filter((text) => text?.length > 0)
29
+ .map((select) => select.options[select.selectedIndex])
30
+ .filter((option) => option.value.length > 0)
31
+ .map((option) => ({ text: option.text, value: option.value}))
31
32
  }
32
33
 
33
34
  setValues() {
@@ -21,7 +21,12 @@ export default class extends CheckboxSelectAll {
21
21
  var totalPretaxRefund = 0
22
22
  this.checked.forEach((checkbox) => {
23
23
  const returnItemRow = checkbox.closest('tr')
24
- const amount = parseFloat(returnItemRow.querySelector('.refund-amount-input').value)
24
+ const refundAmountInput = returnItemRow.querySelector('.refund-amount-input')
25
+ let amount = parseFloat(refundAmountInput.value)
26
+ if (!Number.isFinite(amount)) {
27
+ amount = 0
28
+ refundAmountInput.value = 0
29
+ }
25
30
  totalPretaxRefund += amount
26
31
  })
27
32
 
@@ -0,0 +1,24 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ threshold: { type: Number, default: 60 }
6
+ }
7
+
8
+ connect() {
9
+ this.handleScroll = this.handleScroll.bind(this)
10
+ window.addEventListener('scroll', this.handleScroll)
11
+ }
12
+
13
+ disconnect() {
14
+ window.removeEventListener('scroll', this.handleScroll)
15
+ }
16
+
17
+ handleScroll() {
18
+ if (window.scrollY > this.thresholdValue) {
19
+ this.element.classList.add('sticky-top')
20
+ } else {
21
+ this.element.classList.remove('sticky-top')
22
+ }
23
+ }
24
+ }
@@ -129,7 +129,7 @@ export default class extends CheckboxSelectAll {
129
129
  const nestingLevel = internalName.split('/').length
130
130
  if (nestingLevel === 1) {
131
131
  const firstOptionKey = Object.keys(this.optionsValue)[0]
132
- const newOptionValues = this.optionsValue[firstOptionKey].values.filter((value) => value !== internalName)
132
+ const newOptionValues = this.optionsValue[firstOptionKey].values.filter((value) => value.text !== internalName)
133
133
  if (newOptionValues.length === 0) {
134
134
  const newOptionsValue = this.optionsValue
135
135
  delete newOptionsValue[firstOptionKey]
@@ -265,12 +265,12 @@ export default class extends CheckboxSelectAll {
265
265
  let name = ''
266
266
  let internalName = name
267
267
  if (i === 0) {
268
- name = variant[keys[i]]
268
+ name = variant[keys[i]].text
269
269
  internalName = name
270
270
  } else {
271
- const namesPath = keys.slice(1, keys.length).map((key) => variant[key])
271
+ const namesPath = keys.slice(1, keys.length).map((key) => variant[key].text)
272
272
  name = namesPath.join(' / ')
273
- internalName = `${variant[keys[0]]}/${namesPath.join('/')}`
273
+ internalName = `${variant[keys[0]].text}/${namesPath.join('/')}`
274
274
  }
275
275
 
276
276
  return { name, internalName }
@@ -495,7 +495,7 @@ export default class extends CheckboxSelectAll {
495
495
  const variantEditButton = variantTarget.querySelector('[data-slot="variantEditButton"]')
496
496
 
497
497
  if (variantEditButton) {
498
- variantEditButton.href = `/admin/products/${this.productIdValue}/variants/${variantId}/edit`
498
+ variantEditButton.href = `${Spree.adminPath}/products/${this.productIdValue}/variants/${variantId}/edit`
499
499
  variantEditButton.classList.remove('invisible')
500
500
  }
501
501
  }
@@ -572,12 +572,12 @@ export default class extends CheckboxSelectAll {
572
572
  refreshParentInputs() {
573
573
  const firstOption = Object.values(this.optionsValue)[0]
574
574
  if (firstOption) {
575
- firstOption.values.forEach((name) => {
575
+ firstOption.values.forEach((option) => {
576
576
  this.currenciesValue.forEach((currency) => {
577
- this.updateParentPriceRange(name, currency)
577
+ this.updateParentPriceRange(option.text, currency)
578
578
  })
579
579
  this.stockLocationsValue.forEach((stockLocationId) => {
580
- this.updateParentStockSum(name, stockLocationId)
580
+ this.updateParentStockSum(option.text, stockLocationId)
581
581
  })
582
582
  this.updateShopLocationCountOnHand()
583
583
  })
@@ -615,11 +615,17 @@ export default class extends CheckboxSelectAll {
615
615
  .find((option) => option.name === key).position
616
616
  inputs.push(positionInput)
617
617
 
618
- const valueInput = document.createElement('input')
619
- valueInput.type = 'hidden'
620
- valueInput.name = `product[variants_attributes][${i}][options][][value]`
621
- valueInput.value = variant[key]
622
- inputs.push(valueInput)
618
+ const optionNameInput = document.createElement('input')
619
+ optionNameInput.type = 'hidden'
620
+ optionNameInput.name = `product[variants_attributes][${i}][options][][option_value_name]`
621
+ optionNameInput.value = variant[key].value
622
+ inputs.push(optionNameInput)
623
+
624
+ const optionIdInput = document.createElement('input')
625
+ optionIdInput.type = 'hidden'
626
+ optionIdInput.name = `product[variants_attributes][${i}][options][][option_value_presentation]`
627
+ optionIdInput.value = variant[key].text
628
+ inputs.push(optionIdInput)
623
629
  })
624
630
 
625
631
  return inputs
@@ -924,8 +930,8 @@ export default class extends CheckboxSelectAll {
924
930
  const optionValuesSelectContainer = template.querySelector('[data-slot="optionValuesSelectContainer"]')
925
931
  const tomSelectOptionValues = optionValues.map((optionValue) => {
926
932
  return {
927
- id: availableOptions.find((availableOption) => availableOption.name === optionValue)?.id,
928
- name: optionValue
933
+ id: optionValue.value,
934
+ name: optionValue.text,
929
935
  }
930
936
  })
931
937
 
@@ -948,8 +954,8 @@ export default class extends CheckboxSelectAll {
948
954
  values.forEach((value) => {
949
955
  const template = this.optionValueTemplateTarget.content.cloneNode(true)
950
956
  const optionValueNameEl = template.querySelector('[data-slot="optionValueName"]')
951
- optionValueNameEl.textContent = value
952
- optionValueNameEl.dataset.name = value
957
+ optionValueNameEl.textContent = value.text
958
+ optionValueNameEl.dataset.name = value.text
953
959
 
954
960
  templates.push(template)
955
961
  })
@@ -20,7 +20,8 @@ export default class ActiveStorageUpload extends BasePlugin {
20
20
  limit: 0,
21
21
  timeout: 30 * 1000,
22
22
  directUploadUrl: null,
23
- headers: {}
23
+ headers: {},
24
+ crop: false
24
25
  }
25
26
 
26
27
  this.opts = Object.assign({}, defaultOptions, opts)
@@ -37,10 +38,19 @@ export default class ActiveStorageUpload extends BasePlugin {
37
38
 
38
39
  install() {
39
40
  this.uppy.addUploader(this.handleUpload)
41
+ this.uppy.on('file-editor:complete', this.onEditorComplete)
40
42
  }
41
43
 
42
44
  uninstall() {
43
45
  this.uppy.removeUploader(this.handleUpload)
46
+ this.uppy.off('file-editor:complete', this.onEditorComplete)
47
+ }
48
+
49
+ onEditorComplete = (updatedFile) => {
50
+ this.handleUpload([updatedFile.id])
51
+
52
+ // call directly upload method after editing image
53
+ return this.uploadFiles([updatedFile])
44
54
  }
45
55
 
46
56
  handleUpload(fileIDs) {
@@ -48,6 +58,10 @@ export default class ActiveStorageUpload extends BasePlugin {
48
58
  this.uppy.log("[ActiveStorage] No files to upload!")
49
59
  return Promise.resolve()
50
60
  }
61
+ // do not upload before editing is done
62
+ if (this.opts.crop) {
63
+ return Promise.resolve()
64
+ }
51
65
 
52
66
  this.uppy.log("[ActiveStorage] Uploading...")
53
67
  const files = fileIDs.map(fileID => this.uppy.getFile(fileID))
@@ -115,8 +129,6 @@ export default class ActiveStorageUpload extends BasePlugin {
115
129
  directUploadSignedId: blob.signed_id,
116
130
  }
117
131
 
118
- this.uppy.setFileState(file.id, { response })
119
-
120
132
  this.uppy.emit("upload-success", file, blob)
121
133
 
122
134
  return resolve(file)
@@ -2,7 +2,7 @@
2
2
  <% height ||= 300 %>
3
3
  <% crop ||= false unless defined?(crop) %>
4
4
  <% auto_submit ||= false %>
5
- <% can_delete ||= true unless defined?(can_delete) %>
5
+ <% show_delete_button = defined?(can_delete) ? can_delete : true %>
6
6
  <% css ||= '' %>
7
7
 
8
8
  <% if field_name && form %>
@@ -36,7 +36,7 @@
36
36
 
37
37
  <% if crop %>
38
38
  <span class="text-muted ml-2 text-center small mt-2">
39
- Recommended size: <%= width %>x<%= height %>px
39
+ <%= Spree.t('admin.recommended_size') %>: <%= width %>x<%= height %>px
40
40
  </span>
41
41
  <% end %>
42
42
  </div>
@@ -48,7 +48,7 @@
48
48
  <%= Spree.t('actions.select_file') %>
49
49
  </button>
50
50
 
51
- <% if can_delete %>
51
+ <% if show_delete_button %>
52
52
  <button type="button" class="btn btn-danger ml-auto" data-action="active-storage-upload#remove" data-turbo-confirm="<%= Spree.t(:are_you_sure) %>">
53
53
  <%= icon('trash') %>
54
54
 
@@ -1,11 +1,7 @@
1
1
  <tr id="spree_coupon_code_<%= coupon_code[0] %>">
2
2
  <td class="w-50">
3
- <div data-controller="clipboard">
4
- <code data-clipboard-target="source"><%= coupon_code[1].upcase %></code>
5
- <button type="button" class="btn btn-light p-1 ml-2 with-tip" data-action="clipboard#copy" title="<%= Spree.t('admin.copy_to_clipboard') %>">
6
- <%= icon('copy-check', class: 'mr-0 font-size-sm') %>
7
- </button>
8
- </div>
3
+ <code><%= coupon_code[1].upcase %></code>
4
+ <%= clipboard_component(coupon_code[1].upcase) %>
9
5
  </td>
10
6
  <td class="w-25 font-size-sm"><%= active_badge(coupon_code[2] == 'used') %></td>
11
7
  <td class="w-25 font-size-sm"><%= local_time(coupon_code[3]) %></td>
@@ -10,5 +10,5 @@
10
10
  coupon_code[0].upcase,
11
11
  coupon_code[1],
12
12
  coupon_code[2]
13
- ]).strip.html_safe %>
13
+ ]).strip %>
14
14
  <%- end -%>
@@ -3,11 +3,10 @@
3
3
  <%= @custom_domain.url %>
4
4
  <% end %>
5
5
 
6
- <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
7
-
8
6
  <%= form_for @object, url: spree.admin_custom_domain_path(@object) do |f| %>
9
7
  <div class="row">
10
- <div class="col-lg-6">
8
+ <div class="col-lg-6 offset-lg-3">
9
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
11
10
  <%= render partial: 'form', locals: { f: f } %>
12
11
  <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { f: f } %>
13
12
  </div>
@@ -3,11 +3,10 @@
3
3
  <%= Spree.t(:new_custom_domain) %>
4
4
  <% end %>
5
5
 
6
- <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
7
-
8
6
  <%= form_for @object, url: spree.admin_custom_domains_path do |f| %>
9
7
  <div class="row">
10
- <div class="col-lg-6">
8
+ <div class="col-lg-6 offset-lg-3">
9
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
11
10
  <%= render partial: 'form', locals: { f: f } %>
12
11
  <%= render partial: 'spree/admin/shared/new_resource_links' %>
13
12
  </div>
@@ -12,8 +12,7 @@
12
12
  </span>
13
13
  </span>
14
14
  </div>
15
- <div class="progress">
16
- <div class="progress-bar bg-success" style="width: <%= current_store.setup_percentage %>%" role="progressbar" aria-valuenow="<%= current_store.setup_percentage %>" aria-valuemin="0" aria-valuemax="100"></div>
17
- </div>
15
+
16
+ <%= progress_bar_component(current_store.setup_percentage) %>
18
17
  </div>
19
18
  </div>
@@ -12,15 +12,9 @@
12
12
  <% end %>
13
13
 
14
14
  <div class="card-body px-2 pt-0 pb-2">
15
- <div class="flex my-2 p-1 w-100 rounded border" data-controller="clipboard" data-clipboard-success-content-value="Copied!">
16
- <% if current_store.default_custom_domain&.active? %>
17
- <%= text_field_tag :custom_domain, current_store.default_custom_domain.url, readonly: true, class: 'form-control-plaintext py-0', data: { clipboard_target: 'source' } %>
18
- <% else %>
19
- <%= text_field_tag :domain, current_store.url, readonly: true, class: 'form-control-plaintext py-0', data: { clipboard_target: 'source' } %>
20
- <% end %>
21
- <button data-action="clipboard#copy" data-clipboard-target="button" class="btn btn-sm py-1 hover-gray">
22
- Copy
23
- </button>
15
+ <div class="flex my-2 p-1 w-100 rounded border">
16
+ <%= text_field_tag :domain, current_store.url_or_custom_domain, readonly: true, class: 'form-control-plaintext py-0' %>
17
+ <%= clipboard_component(current_store.url_or_custom_domain) %>
24
18
  </div>
25
19
 
26
20
  <div class="d-flex">
@@ -0,0 +1,30 @@
1
+ <div class="card mb-4">
2
+ <div class="card-header">
3
+ <h5 class="card-title">
4
+ <%= Spree.t(:gift_card_batch) %>
5
+ </h5>
6
+ </div>
7
+
8
+ <div class="card-body">
9
+ <div class="form-group">
10
+ <%= f.label :prefix, raw(Spree.t('admin.gift_card_batches.prefix') + required_span_tag)%>
11
+ <%= f.text_field :prefix, class: 'form-control', disabled: f.object.persisted? %>
12
+ <%= f.error_message_on :prefix %>
13
+ </div>
14
+ <div class="form-group">
15
+ <%= f.label :amount, raw(Spree.t(:amount) + required_span_tag) %>
16
+ <%= f.number_field :amount, class: 'form-control', disabled: f.object.persisted?, required: true %>
17
+ <%= f.error_message_on :amount %>
18
+ </div>
19
+ <div class="form-group">
20
+ <%= f.label :codes_count, raw(Spree.t('admin.gift_card_batches.codes_count') + required_span_tag) %>
21
+ <%= f.number_field :codes_count, class: 'form-control', disabled: f.object.persisted?, required: true, min: 1, max: Spree::Config[:gift_card_batch_limit] %>
22
+ <%= f.error_message_on :codes_count %>
23
+ </div>
24
+ <div class="form-group">
25
+ <%= f.label :expires_at, Spree.t(:expires_at) %>
26
+ <%= f.date_field :expires_at, class: 'form-control', disabled: f.object.persisted? %>
27
+ <%= f.error_message_on :expires_at %>
28
+ </div>
29
+ </div>
30
+ </div>
@@ -0,0 +1,15 @@
1
+ <% content_for :page_title do %>
2
+ <%= page_header_back_button spree.admin_gift_cards_path %>
3
+ <%= Spree.t('admin.gift_card_batches.new_gift_card_batch') %>
4
+ <% end %>
5
+
6
+ <%= form_for [:admin, @object] do |f| %>
7
+ <div class="row">
8
+ <div class="col-lg-6 offset-lg-3">
9
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %>
10
+
11
+ <%= render 'form', f: f %>
12
+ <%= render 'spree/admin/shared/new_resource_links' %>
13
+ </div>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,43 @@
1
+ <% frame_name ||= nil %>
2
+ <% filter_form_url = params[:user_id].present? ? spree.admin_user_gift_cards_path(params[:user_id]) : spree.admin_gift_cards_path %>
3
+
4
+ <%= search_form_for @search,
5
+ url: filter_form_url,
6
+ class: "filter-wrap",
7
+ data: {
8
+ controller: "filters reveal",
9
+ reveal_hidden_class: "d-none",
10
+ filters_url_value: request.url
11
+ } do |f| %>
12
+ <%= hidden_field_tag :frame_name, frame_name if frame_name.present? %>
13
+
14
+ <div class="d-flex flex-column flex-lg-row gap-2">
15
+ <%= render 'spree/admin/shared/filters_search_bar', param: :code_eq, label: Spree.t(:code) %>
16
+ <%= render "table_filter_dropdown" %>
17
+ <%= render "spree/admin/shared/filters_button" %>
18
+ </div>
19
+ <div data-reveal-target="item" class="d-none" id="table-filter">
20
+ <% if params[:user_id].blank? %>
21
+ <div class="form-group">
22
+ <%= f.label :users_email_eq, Spree.t(:email) %>
23
+ <%= f.text_field :users_email_eq, class: "form-control", data: {filters_target: :input} %>
24
+ </div>
25
+ <% end %>
26
+
27
+ <div class="form-group">
28
+ <%= f.label :batch_prefix_eq, Spree.t('admin.gift_card_batches.prefix') %>
29
+ <%= f.text_field :batch_prefix_eq, class: "form-control", data: {filters_target: :input} %>
30
+ </div>
31
+
32
+ <div class="form-actions">
33
+ <%= turbo_save_button_tag Spree.t(:filter_results) do %>
34
+ <%= icon("search") %>
35
+ <%= Spree.t(:filter_results) %>
36
+ <% end %>
37
+ </div>
38
+ </div>
39
+
40
+ <%= render "spree/admin/shared/filter_badge_template" %>
41
+
42
+ <div data-filters-target="badgesContainer" class="filter-badges-container"></div>
43
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <% unless f.object.persisted? %>
2
+ <div class="form-group">
3
+ <%= f.label :code do %>
4
+ <%= Spree.t(:code) %>
5
+ <%= help_bubble(Spree.t('admin.gift_cards.code_help_text')) %>
6
+ <% end %>
7
+ <%= f.text_field :code, class: 'form-control' %>
8
+ <%= f.error_message_on :code %>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="form-group">
13
+ <%= f.label :amount, raw(Spree.t(:amount) + required_span_tag) %>
14
+ <%= f.text_field :amount, class: 'form-control', required: true %>
15
+ <%= f.error_message_on :amount %>
16
+ </div>
17
+
18
+ <div class="form-group">
19
+ <%= f.label :user_id, Spree.t(:customer) %>
20
+ <%= tom_select_tag 'gift_card[user_id]',
21
+ active_option: @user&.id || @object.user_id,
22
+ include_blank: true,
23
+ options: users_for_select_options %>
24
+ </div>
25
+ <div class="form-group">
26
+ <%= f.label :expires_at, Spree.t(:expires_at) %>
27
+ <%= f.date_field :expires_at, class: 'form-control' %>
28
+ <%= f.error_message_on :expires_at %>
29
+ </div>
30
+
@@ -0,0 +1,28 @@
1
+ <% link = @user.present? ? spree.admin_user_gift_card_path(gift_card, user_id: @user.id) : spree.admin_gift_card_path(gift_card) %>
2
+
3
+ <tr id="<%= spree_dom_id gift_card %>" data-controller="row-link">
4
+ <td class="w-25 py-0">
5
+ <%= link_to gift_card.code.upcase, link, class: 'font-weight-bold d-block w-100 h-100 py-2', data: { 'turbo-frame': '_top', row_link_target: :link } %>
6
+ </td>
7
+ <td class="w-10 cursor-pointer" data-action="click->row-link#openLink"><%= gift_card.display_amount %></td>
8
+ <td class="w-10 cursor-pointer" data-action="click->row-link#openLink"><%= gift_card.display_amount_used %></td>
9
+ <td class="w-10 cursor-pointer" data-action="click->row-link#openLink"><%= gift_card.currency.upcase %></td>
10
+ <td class="w-10 cursor-pointer" data-action="click->row-link#openLink"><%= active_badge(gift_card.active?, label: gift_card.display_state) %></td>
11
+ <td class="w-10 cursor-pointer" data-action="click->row-link#openLink">
12
+ <% if gift_card.expires_at.present? %>
13
+ <%= local_date(gift_card.expires_at) %>
14
+ <% else %>
15
+ <span class="text-muted">
16
+ <%= Spree.t(:not_available) %>
17
+ </span>
18
+ <% end %></td>
19
+ <td class="w-15 cursor-pointer" data-action="click->row-link#openLink">
20
+ <% if gift_card.user.present? %>
21
+ <%= gift_card.user.email %>
22
+ <% else %>
23
+ <span class="text-muted">
24
+ <%= Spree.t(:not_available) %>
25
+ </span>
26
+ <% end %>
27
+ </td>
28
+ </tr>