spree_core 5.5.4 → 5.6.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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +12 -2
  3. data/app/helpers/spree/currency_helper.rb +1 -4
  4. data/app/helpers/spree/locale_helper.rb +18 -13
  5. data/app/helpers/spree/localized_names_helper.rb +25 -0
  6. data/app/mailers/spree/admin_user_mailer.rb +71 -0
  7. data/app/mailers/spree/base_mailer.rb +73 -11
  8. data/app/mailers/spree/export_mailer.rb +7 -7
  9. data/app/mailers/spree/import_mailer.rb +19 -0
  10. data/app/mailers/spree/invitation_mailer.rb +16 -11
  11. data/app/mailers/spree/report_mailer.rb +6 -6
  12. data/app/mailers/spree/webhook_mailer.rb +7 -6
  13. data/app/models/concerns/spree/admin_user_methods.rb +44 -0
  14. data/app/models/concerns/spree/legacy_multi_store_support.rb +48 -0
  15. data/app/models/concerns/spree/presentation_translatable.rb +39 -0
  16. data/app/models/concerns/spree/product_scopes.rb +21 -3
  17. data/app/models/concerns/spree/single_store_resource.rb +5 -0
  18. data/app/models/concerns/spree/store_scoped_resource.rb +10 -0
  19. data/app/models/concerns/spree/stores/markets.rb +9 -1
  20. data/app/models/concerns/spree/stores/setup.rb +37 -23
  21. data/app/models/concerns/spree/translatable_resource.rb +95 -5
  22. data/app/models/concerns/spree/user_methods.rb +27 -2
  23. data/app/models/spree/ability.rb +5 -4
  24. data/app/models/spree/allowed_origin.rb +31 -0
  25. data/app/models/spree/category.rb +12 -2
  26. data/app/models/spree/channel/gating.rb +67 -0
  27. data/app/models/spree/channel.rb +1 -0
  28. data/app/models/spree/classification.rb +12 -0
  29. data/app/models/spree/country.rb +22 -1
  30. data/app/models/spree/credit_card.rb +29 -0
  31. data/app/models/spree/currency.rb +47 -0
  32. data/app/models/spree/current.rb +12 -1
  33. data/app/models/spree/customer_group.rb +1 -0
  34. data/app/models/spree/export.rb +19 -0
  35. data/app/models/spree/exports/coupon_codes.rb +1 -2
  36. data/app/models/spree/import.rb +84 -1
  37. data/app/models/spree/import_row.rb +7 -1
  38. data/app/models/spree/imports/product_translations.rb +6 -0
  39. data/app/models/spree/locale.rb +104 -0
  40. data/app/models/spree/option_type.rb +11 -13
  41. data/app/models/spree/option_value.rb +5 -17
  42. data/app/models/spree/order.rb +19 -2
  43. data/app/models/spree/payment_method.rb +6 -5
  44. data/app/models/spree/policy.rb +2 -1
  45. data/app/models/spree/product.rb +23 -2
  46. data/app/models/spree/promotion/rules/channel.rb +37 -0
  47. data/app/models/spree/promotion/rules/market.rb +37 -0
  48. data/app/models/spree/promotion.rb +5 -3
  49. data/app/models/spree/promotion_handler/promotion_duplicator.rb +1 -1
  50. data/app/models/spree/role_user.rb +4 -0
  51. data/app/models/spree/search_provider/meilisearch.rb +22 -9
  52. data/app/models/spree/setup_task.rb +12 -0
  53. data/app/models/spree/setup_tasks.rb +86 -0
  54. data/app/models/spree/shipment.rb +38 -1
  55. data/app/models/spree/shipment_handler.rb +1 -1
  56. data/app/models/spree/stock/availability_validator.rb +1 -1
  57. data/app/models/spree/stock/quantifier.rb +21 -1
  58. data/app/models/spree/stock_item.rb +2 -1
  59. data/app/models/spree/stock_location.rb +4 -1
  60. data/app/models/spree/store.rb +45 -9
  61. data/app/models/spree/store_payment_method.rb +5 -1
  62. data/app/models/spree/store_promotion.rb +4 -0
  63. data/app/models/spree/taxon.rb +112 -9
  64. data/app/models/spree/taxonomy.rb +1 -1
  65. data/app/models/spree/translations/batch.rb +104 -0
  66. data/app/models/spree/variant.rb +29 -2
  67. data/app/models/spree/webhook_delivery.rb +0 -22
  68. data/app/presenters/spree/data_feeds/google_presenter.rb +1 -28
  69. data/app/presenters/spree/search_provider/product_presenter.rb +3 -0
  70. data/app/services/spree/carts/complete.rb +3 -0
  71. data/app/services/spree/fulfillments/create.rb +259 -0
  72. data/app/services/spree/locales/set_fallback_locale_for_store.rb +7 -1
  73. data/app/services/spree/locales.rb +52 -0
  74. data/app/services/spree/products/prepare_nested_attributes.rb +40 -12
  75. data/app/services/spree/shipments/update.rb +1 -1
  76. data/app/services/spree/stock_reservations/reserve.rb +4 -1
  77. data/app/services/spree/taxons/add_products.rb +3 -0
  78. data/app/services/spree/taxons/remove_products.rb +3 -0
  79. data/app/subscribers/spree/admin_user_email_subscriber.rb +29 -0
  80. data/app/subscribers/spree/import_email_subscriber.rb +26 -0
  81. data/app/views/layouts/spree/base_mailer.html.erb +14 -17
  82. data/app/views/spree/admin_user_mailer/confirmation_email.html.erb +7 -0
  83. data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +8 -0
  84. data/app/views/spree/export_mailer/export_done.html.erb +6 -14
  85. data/app/views/spree/import_mailer/import_done.html.erb +12 -0
  86. data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +4 -12
  87. data/app/views/spree/invitation_mailer/invitation_email.html.erb +11 -24
  88. data/app/views/spree/report_mailer/report_done.html.erb +6 -14
  89. data/app/views/spree/shared/_base_mailer_footer.html.erb +15 -8
  90. data/app/views/spree/shared/_base_mailer_header.html.erb +7 -5
  91. data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +211 -273
  92. data/app/views/spree/shared/_mailer_button.html.erb +10 -0
  93. data/app/views/spree/shared/_mailer_hero.html.erb +12 -0
  94. data/app/views/spree/shared/_purchased_items_styles.html.erb +112 -0
  95. data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +39 -14
  96. data/config/initializers/spree_store_setup_tasks.rb +23 -0
  97. data/config/locales/en.yml +42 -992
  98. data/db/migrate/20260613000001_add_store_id_to_spree_role_users.rb +10 -0
  99. data/db/migrate/20260626000001_add_store_id_to_spree_taxons.rb +11 -0
  100. data/db/migrate/20260627000001_add_products_count_to_spree_taxons.rb +6 -0
  101. data/db/migrate/20260628000001_add_store_id_to_spree_promotions.rb +10 -0
  102. data/db/migrate/20260628000002_add_store_id_to_spree_payment_methods.rb +11 -0
  103. data/db/migrate/20260630000001_add_preorder_fields_to_spree_variants.rb +7 -0
  104. data/db/migrate/20260707000001_add_fingerprint_to_spree_credit_cards.rb +39 -0
  105. data/db/migrate/20260710000001_add_import_id_status_index_to_spree_import_rows.rb +7 -0
  106. data/db/migrate/20260711000001_add_preferences_to_spree_exports.rb +7 -0
  107. data/db/sample_data/promotions.rb +1 -1
  108. data/lib/generators/spree/authentication/dummy/templates/create_spree_admin_users.rb.tt +0 -1
  109. data/lib/mobility/plugins/store_based_fallbacks.rb +7 -5
  110. data/lib/spree/core/configuration.rb +1 -0
  111. data/lib/spree/core/controller_helpers/locale.rb +4 -1
  112. data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -0
  113. data/lib/spree/core/dependencies.rb +3 -0
  114. data/lib/spree/core/engine.rb +24 -0
  115. data/lib/spree/core/preferences/preferable.rb +6 -1
  116. data/lib/spree/core/previews/admin_user_preview.rb +22 -0
  117. data/lib/spree/core/previews/export_preview.rb +37 -0
  118. data/lib/spree/core/previews/invitation_preview.rb +47 -0
  119. data/lib/spree/core/previews/preview_data.rb +52 -0
  120. data/lib/spree/core/previews/report_preview.rb +40 -0
  121. data/lib/spree/core/previews/webhook_preview.rb +34 -0
  122. data/lib/spree/core/version.rb +1 -1
  123. data/lib/spree/core.rb +22 -1
  124. data/lib/spree/permitted_attributes.rb +5 -4
  125. data/lib/spree/testing_support/factories/export_factory.rb +4 -0
  126. data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -9
  127. data/lib/spree/testing_support/factories/promotion_factory.rb +1 -9
  128. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +24 -0
  129. data/lib/spree/translations.rb +113 -0
  130. data/lib/spree/upgrades/5_5_to_5_6/manifest.yml +59 -0
  131. data/lib/tasks/role_users.rake +21 -0
  132. data/lib/tasks/single_store_associations.rake +65 -0
  133. data/lib/tasks/taxons.rake +62 -1
  134. metadata +47 -6
  135. data/app/models/concerns/spree/webhook_payload_redaction.rb +0 -88
  136. data/app/models/spree/product/legacy_multi_store_support.rb +0 -40
@@ -7,6 +7,12 @@ module Spree
7
7
  # from other stores are preserved. This prevents accidental data loss when a store
8
8
  # admin updates product categories in their store without affecting other stores.
9
9
  #
10
+ # Variant removal is opt-in: only variants explicitly listed in the
11
+ # `removed_variant_ids` param are marked for destruction (or collected for
12
+ # discontinuation when they have completed orders). Variants merely absent from
13
+ # `variants_attributes` are left untouched, so a partially rendered or broken
14
+ # form can never silently mass-delete variants.
15
+ #
10
16
  # @example
11
17
  # service = Spree::Products::PrepareNestedAttributes.new(
12
18
  # product,
@@ -28,10 +34,13 @@ module Spree
28
34
  end
29
35
 
30
36
  def call
37
+ extract_removed_variant_ids
38
+
31
39
  if params[:variants_attributes]
32
40
  params[:variants_attributes].each do |key, variant_params|
33
41
  existing_variant = variant_params[:id].presence && @product.variants.find_by(id: variant_params[:id])
34
- variants_to_remove.delete(variant_params[:id]) if variant_params[:id].present?
42
+ # a re-submitted variant always wins over a removal request
43
+ variants_to_remove.delete(variant_params[:id].to_s) if variant_params[:id].present?
35
44
 
36
45
  variant_params.delete(:price) # remove legacy price param
37
46
 
@@ -72,17 +81,20 @@ module Spree
72
81
  # ensure the product is owned by a store
73
82
  params[:store_id] = store.id if params[:store_id].blank? && product.store_id.blank?
74
83
 
75
- # Add empty list for option_type_ids and mark variants as removed if there are no variants and options
76
- if params[:variants_attributes].blank? && variants_to_remove.any? && !params.key?(:option_type_ids)
77
- params[:option_type_ids] = []
78
- params[:variants_attributes] = {}
84
+ # The variants matrix was emptied: no variant rows re-submitted, removals sent instead.
85
+ # Option types are detached only when the removal list covers every variant, so a
86
+ # partial (possibly broken) submission never turns the product into a simple one.
87
+ # Variants kept alive by discontinuation still count as removed from the matrix,
88
+ # so the detachment can't hinge on any of them yielding a `_destroy` row.
89
+ if params[:variants_attributes].blank? && variants_to_remove.any? && can_remove_variants?
90
+ attributes = removed_variants_attributes
79
91
 
80
- populate_variants_to_discontinue
81
- variant_ids_to_destroy.each_with_index do |variant_id, index|
82
- params[:variants_attributes][index.to_s] = { id: variant_id, _destroy: '1' }
83
- end
92
+ params[:option_type_ids] = [] if removing_all_variants? && !params.key?(:option_type_ids)
84
93
 
85
- params[:variants_attributes].permit!
94
+ if attributes.any?
95
+ params[:variants_attributes] = attributes
96
+ params[:variants_attributes].permit!
97
+ end
86
98
  end
87
99
 
88
100
  params
@@ -116,8 +128,24 @@ module Spree
116
128
  @product_option_types_to_remove ||= product.product_option_type_ids
117
129
  end
118
130
 
131
+ # Pulls `removed_variant_ids` out of the params so it never reaches Product#update.
132
+ # Must run before any `variants_to_remove` access.
133
+ def extract_removed_variant_ids
134
+ @removed_variant_ids = Array(params.delete(:removed_variant_ids)).map(&:to_s)
135
+ end
136
+
137
+ # Only variants the client explicitly asked to remove, and only ones that
138
+ # actually belong to this product.
119
139
  def variants_to_remove
120
- @variants_to_remove ||= product.variant_ids.map(&:to_s)
140
+ @variants_to_remove ||= (@removed_variant_ids || []).uniq & all_variant_ids
141
+ end
142
+
143
+ def all_variant_ids
144
+ @all_variant_ids ||= product.variant_ids.map(&:to_s)
145
+ end
146
+
147
+ def removing_all_variants?
148
+ (all_variant_ids - variants_to_remove).empty?
121
149
  end
122
150
 
123
151
  def can_update_prices?
@@ -142,7 +170,7 @@ module Spree
142
170
  populate_variants_to_discontinue
143
171
 
144
172
  attributes = {}
145
- last_index = params[:variants_attributes].keys.map(&:to_i).max
173
+ last_index = params[:variants_attributes].presence&.keys&.map(&:to_i)&.max || -1
146
174
  variant_ids_to_destroy.each_with_index do |variant_id, index|
147
175
  attributes[(last_index + 1 + index).to_s] = { id: variant_id, _destroy: '1' }
148
176
  end
@@ -8,7 +8,7 @@ module Spree
8
8
  ActiveRecord::Base.transaction do
9
9
  return failure(shipment) unless shipment.update(shipment_attributes)
10
10
 
11
- if shipment_attributes.key?(:selected_shipping_rate_id)
11
+ if shipment_attributes.key?(:selected_shipping_rate_id) || shipment_attributes.key?(:selected_delivery_rate_id)
12
12
  order = shipment.order
13
13
 
14
14
  # Changing the selected Shipping Rate won't update the cost (for now)
@@ -65,7 +65,10 @@ module Spree
65
65
  next unless variant&.should_track_inventory?
66
66
 
67
67
  stock_item = select_stock_item(variant)
68
- next if stock_item.nil? || stock_item.backorderable?
68
+ # Backorderable and pre-order variants oversell past count_on_hand,
69
+ # so a reservation capped at on-hand stock would wrongly reject them;
70
+ # their cap is enforced by Stock::Quantifier#can_supply? instead.
71
+ next if stock_item.nil? || stock_item.backorderable? || variant.preorder?
69
72
 
70
73
  [line_item, stock_item]
71
74
  end
@@ -33,6 +33,9 @@ module Spree
33
33
  product_ids = products.pluck(:id)
34
34
  taxon_ids.each { |id| Spree::Taxon.reset_counters(id, :classifications) }
35
35
  product_ids.each { |id| Spree::Product.reset_counters(id, :classifications) }
36
+ # Recompute the descendant-inclusive products_count for the taxons and
37
+ # their ancestors (bulk insert skips Classification callbacks).
38
+ Spree::Taxon.recalculate_products_count(taxon_ids)
36
39
 
37
40
  # clear cache & index products
38
41
  Spree::Product.where(id: product_ids).touch_all
@@ -48,6 +48,9 @@ module Spree
48
48
  # update counter caches
49
49
  taxon_ids.each { |id| Spree::Taxon.reset_counters(id, :classifications) }
50
50
  product_ids.each { |id| Spree::Product.reset_counters(id, :classifications) }
51
+ # Recompute the descendant-inclusive products_count for the taxons and
52
+ # their ancestors (delete_all skips Classification callbacks).
53
+ Spree::Taxon.recalculate_products_count(taxon_ids)
51
54
 
52
55
  # clear cache & index products
53
56
  Spree::Product.where(id: product_ids).touch_all
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class AdminUserEmailSubscriber < Spree::Subscriber
5
+ subscribes_to 'admin_user.password_reset_requested'
6
+
7
+ def handle(event)
8
+ user = Spree.admin_user_class.find_by(email: event.payload['email'])
9
+ return unless user
10
+
11
+ store = find_store(event)
12
+
13
+ AdminUserMailer.password_reset_email(
14
+ user,
15
+ event.payload['reset_token'],
16
+ store,
17
+ redirect_url: event.payload['redirect_url']
18
+ ).deliver_later
19
+ end
20
+
21
+ private
22
+
23
+ def find_store(event)
24
+ Spree::Store.find_by_prefix_id(event.payload['store_id']) ||
25
+ Spree::Current.store ||
26
+ Spree::Store.default
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Emails the import's owner when processing finishes — including after a
5
+ # failed-rows retry pass, which publishes `import.completed` again.
6
+ # App-created imports (API key without an admin creator) have no recipient
7
+ # and send nothing.
8
+ #
9
+ # We use async: false because this subscriber only enqueues the mail job.
10
+ class ImportEmailSubscriber < Spree::Subscriber
11
+ subscribes_to 'import.completed', async: false
12
+
13
+ on 'import.completed', :send_import_done_email
14
+
15
+ def send_import_done_email(event)
16
+ import_id = event.payload['id']
17
+ return unless import_id
18
+
19
+ import = Spree::Import.find_by_prefix_id(import_id)
20
+ return unless import
21
+ return if import.user&.email.blank?
22
+
23
+ Spree::ImportMailer.import_done(import).deliver_later
24
+ end
25
+ end
26
+ end
@@ -1,46 +1,43 @@
1
1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
- <html>
2
+ <html lang="<%= I18n.locale %>">
3
3
  <head>
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
5
5
  <meta name="x-apple-disable-message-reformatting"/>
6
6
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
7
+ <meta name="color-scheme" content="light"/>
8
+ <meta name="supported-color-schemes" content="light"/>
7
9
 
8
10
  <%= render partial: 'spree/shared/base_mailer_stylesheets' %>
11
+ <%# Per-email styles — templates add their own via `content_for :head` %>
12
+ <%= yield :head %>
9
13
  </head>
10
- <body>
11
- <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
14
+ <body class="email-bg" style="margin: 0; padding: 0; -webkit-text-size-adjust: none;" bgcolor="#FFFFFF">
15
+ <table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation" bgcolor="#FFFFFF">
12
16
  <tr>
13
17
  <td align="center">
14
18
  <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
15
- <%= render partial: 'spree/shared/base_mailer_header' %>
16
19
  <!-- Email Body -->
17
20
  <tr>
18
- <td class="email-body" width="570" cellpadding="0" cellspacing="0">
19
- <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
20
- <!-- Body content -->
21
- <tbody>
21
+ <td class="email-card" bgcolor="#F6F6F6">
22
+ <%= render partial: 'spree/shared/base_mailer_header' %>
23
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
22
24
  <tr>
23
- <td class="content-cell">
24
- <div class="f-fallback">
25
- <%= yield %>
26
- </div>
25
+ <td class="content-cell f-fallback" align="left">
26
+ <%= yield %>
27
27
  </td>
28
28
  </tr>
29
- </tbody>
30
29
  </table>
31
30
  </td>
32
31
  </tr>
32
+ <!-- Email Footer -->
33
33
  <tr>
34
- <td>
34
+ <td class="email-footer" align="center">
35
35
  <%= render partial: 'spree/shared/base_mailer_footer' %>
36
36
  </td>
37
37
  </tr>
38
38
  </table>
39
39
  </td>
40
40
  </tr>
41
- <tr>
42
- <td height="60px">&nbsp;</td>
43
- </tr>
44
41
  </table>
45
42
  </body>
46
43
  </html>
@@ -0,0 +1,7 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('admin_user_mailer.confirmation_email.action') %></h1>
3
+ <p class="greeting"><%= Spree.t('admin_user_mailer.confirmation_email.greeting', name: @user.first_name.presence || @user.email) %></p>
4
+ <p><%= Spree.t('admin_user_mailer.confirmation_email.instructions', store_name: @current_store.name) %></p>
5
+ <%= render 'spree/shared/mailer_button', url: @confirmation_url, label: Spree.t('admin_user_mailer.confirmation_email.action') %>
6
+ <p class="sub"><%= Spree.t('admin_user_mailer.confirmation_email.ignore_notice') %></p>
7
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('admin_user_mailer.password_reset_email.action') %></h1>
3
+ <p class="greeting"><%= Spree.t('admin_user_mailer.password_reset_email.greeting', name: @user.first_name.presence || @user.email) %></p>
4
+ <p><%= Spree.t('admin_user_mailer.password_reset_email.instructions', store_name: @current_store.name) %></p>
5
+ <%= render 'spree/shared/mailer_button', url: @reset_url, label: Spree.t('admin_user_mailer.password_reset_email.action') %>
6
+ <p class="sub"><%= Spree.t('admin_user_mailer.password_reset_email.expiry_notice') %></p>
7
+ <p class="sub"><%= Spree.t('admin_user_mailer.password_reset_email.ignore_notice') %></p>
8
+ <% end %>
@@ -1,15 +1,7 @@
1
- <h1>
2
- <%= Spree.t('export_mailer.greeting', user_name: @export.user&.first_name) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('export_mailer.export_done.message') %>
6
- </p>
7
- <% if spree.respond_to?(:admin_export_url) %>
8
- <p>
9
- <%= link_to @export.attachment.filename, spree.admin_export_url(@export, host: @current_store.formatted_url) %>
10
- </p>
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('export_mailer.greeting', user_name: @export.user&.first_name) %></h1>
3
+ <p><%= Spree.t('export_mailer.export_done.message') %></p>
4
+ <% if @export.results_url.present? %>
5
+ <%= render 'spree/shared/mailer_button', url: @export.results_url, label: @export.attachment.filename %>
6
+ <% end %>
11
7
  <% end %>
12
- <p>
13
- <%= Spree.t('export_mailer.thanks') %><br />
14
- <%= "#{current_store.name}" %>
15
- </p>
@@ -0,0 +1,12 @@
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('import_mailer.greeting', user_name: @import.user&.first_name) %></h1>
3
+ <p><%= Spree.t('import_mailer.import_done.message', completed_count: @import.rows_status_counts['completed'] || 0) %></p>
4
+ <% failed_count = @import.rows_status_counts['failed'] || 0 %>
5
+ <% if failed_count > 0 %>
6
+ <p><strong><%= Spree.t('import_mailer.import_done.failed_message', failed_count: failed_count) %></strong></p>
7
+ <% end %>
8
+ <% results_url = @import.results_page_url %>
9
+ <% if results_url.present? %>
10
+ <%= render 'spree/shared/mailer_button', url: results_url, label: Spree.t('import_mailer.import_done.view_results') %>
11
+ <% end %>
12
+ <% end %>
@@ -1,12 +1,4 @@
1
- <h1>
2
- <%= Spree.t('invitation_mailer.greeting', user_name: @invitation.inviter.first_name) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('invitation_mailer.invitation_accepted.body', invitee_name: @invitation.invitee&.name, resource_name: @invitation.resource.name) %>
6
- </p>
7
-
8
- <p>
9
- <%= Spree.t('invitation_mailer.thanks') %><br />
10
- <%= current_store.name %>
11
- </p>
12
-
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('invitation_mailer.greeting', user_name: @invitation.inviter.first_name) %></h1>
3
+ <p><%= Spree.t('invitation_mailer.invitation_accepted.body', invitee_name: @invitation.invitee&.name, resource_name: @invitation.resource.name) %></p>
4
+ <% end %>
@@ -1,25 +1,12 @@
1
- <h1>
2
- <%= Spree.t('invitation_mailer.greeting', user_name: @invitation.invitee&.first_name || @invitation.email) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('invitation_mailer.invitation_email.body', inviter_name: @invitation.inviter.name, resource_name: @invitation.resource.name) %>
6
- </p>
7
- <p>
8
- <%= Spree.t('invitation_mailer.invitation_email.link_description') %>
9
- </p>
10
-
11
- <% if spree.respond_to?(:admin_invitation_url) %>
12
- <p>
13
- <%= link_to Spree.t(:accept), spree.admin_invitation_url(@invitation, token: @invitation.token, host: @invitation.store.formatted_url) %>
14
- </p>
15
- <% else %>
16
- <p>
17
- <%= link_to Spree.t(:accept), main_app.admin_invitation_acceptance_url(@invitation) %>
18
- </p>
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('invitation_mailer.invitation_email.subject', resource_name: @invitation.resource.name) %></h1>
3
+ <p class="greeting"><%= Spree.t('invitation_mailer.greeting', user_name: @invitation.invitee&.first_name || @invitation.email) %></p>
4
+ <p><%= Spree.t('invitation_mailer.invitation_email.body', inviter_name: @invitation.inviter.name, resource_name: @invitation.resource.name) %></p>
5
+ <p><%= Spree.t('invitation_mailer.invitation_email.link_description') %></p>
6
+ <% accept_url = if spree.respond_to?(:admin_invitation_url)
7
+ spree.admin_invitation_url(@invitation, token: @invitation.token, host: @invitation.store.formatted_url)
8
+ else
9
+ main_app.admin_invitation_acceptance_url(@invitation)
10
+ end %>
11
+ <%= render 'spree/shared/mailer_button', url: accept_url, label: Spree.t(:accept) %>
19
12
  <% end %>
20
-
21
- <p>
22
- <%= Spree.t('invitation_mailer.thanks') %><br />
23
- <%= current_store.name %>
24
- </p>
25
-
@@ -1,15 +1,7 @@
1
- <h1>
2
- <%= Spree.t('report_mailer.greeting', user_name: @report.user&.first_name) %>
3
- </h1>
4
- <p>
5
- <%= Spree.t('report_mailer.report_done.message') %>
6
- </p>
7
- <% if spree.respond_to?(:admin_report_url) %>
8
- <p>
9
- <%= link_to @report.attachment.filename, spree.admin_report_url(@report, host: @current_store.formatted_url) %>
10
- </p>
1
+ <%= render layout: 'spree/shared/mailer_hero' do %>
2
+ <h1><%= Spree.t('report_mailer.greeting', user_name: @report.user&.first_name) %></h1>
3
+ <p><%= Spree.t('report_mailer.report_done.message') %></p>
4
+ <% if spree.respond_to?(:admin_report_url) %>
5
+ <%= render 'spree/shared/mailer_button', url: spree.admin_report_url(@report, host: @current_store.formatted_url), label: @report.attachment.filename %>
6
+ <% end %>
11
7
  <% end %>
12
- <p>
13
- <%= Spree.t('report_mailer.thanks') %><br />
14
- <%= "#{current_store.name}" %>
15
- </p>
@@ -1,12 +1,19 @@
1
- <!-- You can override this template to design your own footer.
2
- <table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
1
+ <!-- You can override this template to design your own footer. -->
2
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
3
3
  <tr>
4
- <td class="content-cell" align="center">
5
- <p class="f-fallback sub align-center">
6
- <a href="#">Terms</a> | <a href="#">Privacy</a> |
7
- <a href="#">Unsubscribe</a>
8
- </p>
4
+ <td align="center">
5
+ <div class="footer_content f-fallback">
6
+ <p class="footer_brand">
7
+ <%= link_to current_store.name, current_store.storefront_url %>
8
+ </p>
9
+ <% if current_store.address.present? %>
10
+ <p class="footer_meta"><%= current_store.address %></p>
11
+ <% end %>
12
+ <% if current_store.customer_support_email.present? %>
13
+ <p class="footer_meta"><%= mail_to current_store.customer_support_email %></p>
14
+ <% end %>
15
+ <p class="footer_note">&copy; <%= Time.current.year %> <%= current_store.name %></p>
16
+ </div>
9
17
  </td>
10
18
  </tr>
11
19
  </table>
12
- -->
@@ -1,6 +1,8 @@
1
1
  <!-- You can override this template to design your own header. -->
2
- <tr>
3
- <td class="email-masthead">
4
- <%= render 'spree/shared/mailer_logo', logo: current_store.mailer_logo.attached? ? current_store.mailer_logo : current_store.logo %>
5
- </td>
6
- </tr>
2
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
3
+ <tr>
4
+ <td class="email-masthead" align="center">
5
+ <%= render 'spree/shared/mailer_logo', logo: current_store.mailer_logo.attached? ? current_store.mailer_logo : current_store.logo, height: 32 %>
6
+ </td>
7
+ </tr>
8
+ </table>