spree_storefront 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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +7 -1
  3. data/app/controllers/spree/account/gift_cards_controller.rb +15 -0
  4. data/app/controllers/spree/checkout_controller.rb +12 -6
  5. data/app/controllers/spree/line_items_controller.rb +14 -7
  6. data/app/controllers/spree/products_controller.rb +1 -1
  7. data/app/controllers/spree/search_controller.rb +1 -1
  8. data/app/controllers/spree/store_controller.rb +19 -17
  9. data/app/helpers/spree/cart_helper.rb +1 -1
  10. data/app/helpers/spree/checkout_helper.rb +1 -2
  11. data/app/helpers/spree/filters_helper.rb +34 -22
  12. data/app/helpers/spree/products_helper.rb +5 -1
  13. data/app/helpers/spree/storefront_helper.rb +6 -3
  14. data/app/javascript/spree/storefront/controllers/card_validation_controller.js +2 -0
  15. data/app/javascript/spree/storefront/controllers/cart_controller.js +5 -0
  16. data/app/views/devise/passwords/edit.html.erb +1 -1
  17. data/app/views/devise/registrations/new.html.erb +1 -1
  18. data/app/views/devise/shared/_links.html.erb +3 -3
  19. data/app/views/spree/addresses/edit.html.erb +2 -2
  20. data/app/views/spree/checkout/_address.html.erb +25 -38
  21. data/app/views/spree/checkout/_address_list_item.html.erb +14 -0
  22. data/app/views/spree/checkout/_coupon_code.html.erb +3 -3
  23. data/app/views/spree/checkout/_delivery.html.erb +3 -36
  24. data/app/views/spree/checkout/_delivery_backorder_notice.html.erb +10 -0
  25. data/app/views/spree/checkout/_delivery_shipping_rate.html.erb +22 -0
  26. data/app/views/spree/checkout/_line_item.html.erb +1 -1
  27. data/app/views/spree/checkout/_missing_all_line_items.html.erb +2 -2
  28. data/app/views/spree/checkout/_order_lock_version.html.erb +3 -0
  29. data/app/views/spree/checkout/_payment.html.erb +11 -9
  30. data/app/views/spree/checkout/_payment_methods.html.erb +1 -1
  31. data/app/views/spree/checkout/_store_credit.html.erb +1 -1
  32. data/app/views/spree/checkout/_summary.html.erb +45 -44
  33. data/app/views/spree/checkout/payment/_gateway.html.erb +12 -12
  34. data/app/views/spree/checkout/payment/_store_credit.html.erb +2 -2
  35. data/app/views/spree/checkout/update.turbo_stream.erb +1 -3
  36. data/app/views/spree/line_items/destroy.turbo_stream.erb +14 -10
  37. data/app/views/spree/seo/sitemap.xml.erb +2 -2
  38. data/app/views/themes/default/spree/account/_order.html.erb +5 -5
  39. data/app/views/themes/default/spree/account/gift_cards/_gift_card.html.erb +26 -0
  40. data/app/views/themes/default/spree/account/gift_cards/index.html.erb +30 -0
  41. data/app/views/themes/default/spree/checkout/complete.html.erb +1 -1
  42. data/app/views/themes/default/spree/orders/_line_item.html.erb +3 -3
  43. data/app/views/themes/default/spree/page_sections/_featured_posts.html.erb +56 -0
  44. data/app/views/themes/default/spree/page_sections/_featured_product.html.erb +1 -1
  45. data/app/views/themes/default/spree/page_sections/_featured_taxon.html.erb +2 -2
  46. data/app/views/themes/default/spree/page_sections/_featured_taxons.html.erb +4 -5
  47. data/app/views/themes/default/spree/policies/show.html.erb +11 -7
  48. data/app/views/themes/default/spree/posts/_post.html.erb +20 -11
  49. data/app/views/themes/default/spree/products/_add_to_cart_button.html.erb +4 -4
  50. data/app/views/themes/default/spree/products/_details.html.erb +1 -1
  51. data/app/views/themes/default/spree/products/_json_ld.html.erb +1 -1
  52. data/app/views/themes/default/spree/products/_json_ld_variant.html.erb +1 -1
  53. data/app/views/themes/default/spree/products/_media_gallery.html.erb +1 -2
  54. data/app/views/themes/default/spree/products/filters/_availability.html.erb +1 -1
  55. data/app/views/themes/default/spree/products/filters/_price.html.erb +4 -4
  56. data/app/views/themes/default/spree/products/filters/_taxons.erb +1 -1
  57. data/app/views/themes/default/spree/shared/_cart_icon.html.erb +4 -2
  58. data/app/views/themes/default/spree/shared/_cart_pane.html.erb +5 -2
  59. data/app/views/themes/default/spree/shared/_meta_tags.html.erb +2 -2
  60. data/app/views/themes/default/spree/shared/_order_shipment.html.erb +11 -12
  61. data/app/views/themes/default/spree/shared/icons/_discord.html.erb +9 -0
  62. data/config/locales/en.yml +10 -1
  63. data/config/routes.rb +1 -0
  64. data/lib/generators/spree/storefront/theme/templates/model.rb.tt +91 -4
  65. data/lib/spree/storefront/testing_support/cart_utils.rb +30 -0
  66. metadata +16 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1dbc02737b8c05caa58fafb4584ef7c49e619654a1209e2f83c72c95f596961
4
- data.tar.gz: e070440be4bb251b30a0b6d8f3d57f12cd50bd75e564966799eef441c9a3b4aa
3
+ metadata.gz: 7bfebb039f9950d259f5d7807453bef4e385aefc320b97bc6ff01fc1fe0964cf
4
+ data.tar.gz: ac3b738d43b2c19ad49a56995a10976b7353941279c13512f1b5f54febac5e8d
5
5
  SHA512:
6
- metadata.gz: 487843eb1f23fc955b6bc4d45b2a89112edf2d0cb68075f8714e0a1da197b3e2692dcc434859c3e59b53d4286b1af0da65ba532e016559930dcb90e20e7d08a5
7
- data.tar.gz: e791a45a470fe19e42d9daea9ae3b7b5152dec22f65c4bdc2eeb141a2be7bca42b1f099777588de234e44e42fc2a7fc9be6ea6be46ee4034507630897ea9fc48
6
+ metadata.gz: 1d5540addf2ebad7bc9b1ea6ff4ea0a6431ba28a2d96bca3e942e31744b61f555a5a9be91efdaed3825bac6224ab477c0435cca3f210d45df7fcbd0ac309b847
7
+ data.tar.gz: 4d162ae16a80f1115672bba5fcbbcec9e1029f5d56f49e015b99200ce5c05f11275e68bc2d4c74ece38322815dca2a1694ff287f372a2d041c2cff7c01d03fbc
data/Rakefile CHANGED
@@ -11,5 +11,11 @@ task default: :spec
11
11
  desc "Generates a dummy app for testing"
12
12
  task :test_app do
13
13
  ENV['LIB_NAME'] = 'spree/storefront'
14
- Rake::Task['common:test_app'].execute
14
+
15
+ Rake::Task['common:test_app'].execute(
16
+ user_class: 'User',
17
+ authentication: 'devise',
18
+ install_storefront: 'true',
19
+ install_admin: 'false'
20
+ )
15
21
  end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Account
3
+ class GiftCardsController < BaseController
4
+ def index
5
+ @gift_cards = @user.gift_cards.order(created_at: :desc).page(params[:page]).per(25)
6
+ end
7
+
8
+ private
9
+
10
+ def accurate_title
11
+ Spree.t(:gift_cards)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -4,7 +4,6 @@ module Spree
4
4
  include Spree::CheckoutHelper
5
5
  include Spree::CheckoutAnalyticsHelper
6
6
 
7
- before_action :require_user, unless: -> { current_store.prefers_guest_checkout? }
8
7
  before_action :load_order
9
8
  before_action :remove_out_of_stock_items, only: [:edit, :update]
10
9
 
@@ -191,6 +190,8 @@ module Spree
191
190
  redirect_to spree_login_path
192
191
  end
193
192
  end
193
+ elsif !current_store.prefers_guest_checkout?
194
+ require_user(redirect_path: spree_signup_path)
194
195
  end
195
196
 
196
197
  # completed orders shouldn't be updated anymore
@@ -257,7 +258,7 @@ module Spree
257
258
  end
258
259
 
259
260
  def should_restart_checkout?
260
- (@order.quick_checkout? || @order.ship_address.nil?) && (@order.delivery? || @order.payment?)
261
+ (@order.quick_checkout? || (@order.requires_ship_address? && @order.ship_address.nil?)) && (@order.delivery? || @order.payment?)
261
262
  end
262
263
 
263
264
  def restart_checkout
@@ -314,7 +315,9 @@ module Spree
314
315
  @order.bill_address ||= try_spree_current_user.bill_address
315
316
  end
316
317
  # for guest users or users without addresses, we need to build an empty one here
317
- @order.ship_address ||= Address.new(country: current_store.default_country, user: try_spree_current_user)
318
+ if @order.requires_ship_address?
319
+ @order.ship_address ||= Address.new(country: current_store.default_country, user: try_spree_current_user)
320
+ end
318
321
  end
319
322
 
320
323
  def before_delivery
@@ -325,7 +328,11 @@ module Spree
325
328
  end
326
329
 
327
330
  def before_payment
328
- @order.bill_address ||= @order.ship_address.clone
331
+ @order.bill_address ||= if @order.requires_ship_address?
332
+ @order.ship_address.clone
333
+ else
334
+ Spree::Address.new(country: current_store.default_country, user: try_spree_current_user)
335
+ end
329
336
 
330
337
  if @order.checkout_steps.include? 'delivery'
331
338
  packages = @order.shipments.map(&:to_package)
@@ -374,10 +381,9 @@ module Spree
374
381
  end
375
382
 
376
383
  def remove_expired_gift_card
377
- return unless defined?(Spree::GiftCards::Remove)
378
384
  return unless @order.gift_card.present? && @order.gift_card.expired?
379
385
 
380
- Spree::GiftCards::Remove.call(order: @order)
386
+ Spree::Dependencies.gift_card_remove_service.constantize.call(order: @order)
381
387
  end
382
388
  end
383
389
  end
@@ -22,8 +22,6 @@ module Spree
22
22
  quantity: @quantity,
23
23
  options: options)
24
24
 
25
- flash.now[:error] = result.value.errors.full_messages.to_sentence if result.failure?
26
-
27
25
  @line_item = result.value
28
26
 
29
27
  if result.success?
@@ -45,9 +43,12 @@ module Spree
45
43
  quantity = line_item_params[:quantity]&.to_i || 1
46
44
  result = cart_set_item_quantity_service.call(order: @order, line_item: @line_item, quantity: quantity)
47
45
 
48
- @error = result.value.errors.full_messages.to_sentence if result.failure?
49
-
50
- load_line_items if result.success?
46
+ if result.success?
47
+ load_line_items
48
+ else
49
+ @error = result.value.errors.full_messages.to_sentence
50
+ flash.now[:error] = @error
51
+ end
51
52
 
52
53
  respond_to do |format|
53
54
  format.turbo_stream
@@ -57,9 +58,15 @@ module Spree
57
58
 
58
59
  def destroy
59
60
  result = cart_remove_line_item_service.call(order: @order, line_item: @line_item)
60
- load_line_items if result.success?
61
61
 
62
- track_event('product_removed', { line_item: @line_item })
62
+ if result.success?
63
+ load_line_items
64
+
65
+ track_event('product_removed', { line_item: @line_item })
66
+ else
67
+ @error = result.value.errors.full_messages.to_sentence
68
+ flash.now[:error] = @error
69
+ end
63
70
 
64
71
  respond_to do |format|
65
72
  format.turbo_stream
@@ -23,7 +23,7 @@ module Spree
23
23
  load_product
24
24
  # An interesting thing is that since we're querying the translations table (in the multi_search),
25
25
  # when using not default locale, our related products are different for different locales.
26
- @products = current_store.products.active(current_currency).where.not(id: @product.id).
26
+ @products = storefront_products_scope.where.not(id: @product.id).
27
27
  multi_search(@product.name).includes(storefront_products_includes).
28
28
  limit(@section.preferred_max_products_to_show)
29
29
  end
@@ -13,7 +13,7 @@ module Spree
13
13
  @taxons = []
14
14
 
15
15
  if query.present? && query.length >= Spree::Storefront::Config.search_min_query_length
16
- products_scope = current_store.products.active(current_currency).multi_search(query)
16
+ products_scope = storefront_products_scope.multi_search(query)
17
17
  @products = products_scope.includes(storefront_products_includes)
18
18
  @taxons = current_store.taxons.search_by_name(query)
19
19
  end
@@ -123,29 +123,31 @@ module Spree
123
123
  end
124
124
 
125
125
  def storefront_products_scope
126
- current_store.products.active(current_currency)
126
+ @storefront_products_scope ||= current_store.products.active(current_currency)
127
127
  end
128
128
 
129
129
  def default_products_finder_params
130
- taxon = @taxon || current_taxon
130
+ @default_products_finder_params ||= begin
131
+ taxon = @taxon || current_taxon
131
132
 
132
- filter = permitted_products_params.fetch(:filter, {}).dup
133
+ filter = permitted_products_params.fetch(:filter, {}).dup
133
134
 
134
- filter[:taxon_ids] ||= [taxon&.id.to_s].compact
135
- filter[:taxons] = filter[:taxon_ids].join(',')
135
+ filter[:taxon_ids] ||= [taxon&.id.to_s].compact
136
+ filter[:taxons] = filter[:taxon_ids].join(',')
136
137
 
137
- if filter.key?(:min_price) || filter.key?(:max_price)
138
- min_price = filter[:min_price].presence || 0
139
- max_price = filter[:max_price].presence || 'Infinity'
138
+ if filter.key?(:min_price) || filter.key?(:max_price)
139
+ min_price = filter[:min_price].presence || 0
140
+ max_price = filter[:max_price].presence || 'Infinity'
140
141
 
141
- filter[:price] = [min_price, max_price].compact.join(',')
142
- end
142
+ filter[:price] = [min_price, max_price].compact.join(',')
143
+ end
143
144
 
144
- permitted_products_params.merge(
145
- store: current_store,
146
- filter: filter,
147
- currency: current_currency
148
- )
145
+ permitted_products_params.merge(
146
+ store: current_store,
147
+ filter: filter,
148
+ currency: current_currency
149
+ )
150
+ end
149
151
  end
150
152
 
151
153
  def storefront_products
@@ -202,13 +204,13 @@ module Spree
202
204
  redirect_back(fallback_location: default)
203
205
  end
204
206
 
205
- def require_user(return_to = nil)
207
+ def require_user(return_to: nil, redirect_path: nil)
206
208
  return if try_spree_current_user
207
209
 
208
210
  store_location(return_to)
209
211
 
210
212
  respond_to do |format|
211
- format.html { redirect_to spree_login_path }
213
+ format.html { redirect_to redirect_path || spree_login_path }
212
214
  format.turbo_stream { render turbo_stream: turbo_stream.slideover_open('slideover-account', 'account-pane') }
213
215
  end
214
216
  end
@@ -22,7 +22,7 @@ module Spree
22
22
  class: opts[:class],
23
23
  data: {
24
24
  'quantity-picker-target': opts[:action],
25
- action: "click->quantity-picker##{opts[:action]} click->cart#disableCart"
25
+ action: "click->quantity-picker##{opts[:action]} click->cart#disableCart turbo-stream-form:submit-end->cart#enableCart"
26
26
  }
27
27
  )
28
28
  end
@@ -47,8 +47,7 @@ module Spree
47
47
  end
48
48
 
49
49
  def quick_checkout_enabled?(order)
50
- order.payment_required? && order.shipments.count <= 1 &&
51
- (order.digital? || !order.some_digital?) # Either fully digital or not digital at all
50
+ order.quick_checkout_available?
52
51
  end
53
52
 
54
53
  def can_use_store_credit_on_checkout?(order)
@@ -26,19 +26,23 @@ module Spree
26
26
  end
27
27
 
28
28
  def product_filters_aggregations
29
- @product_filters_aggregations ||= storefront_products_for_filters.
30
- joins(variants_including_master: :option_values).
31
- group("#{Spree::OptionValue.table_name}.id").
32
- distinct.
33
- count
29
+ @product_filters_aggregations ||= Rails.cache.fetch(["product-filters-aggregations", storefront_products_for_filters.cache_key_with_version], expires_in: 1.day) do
30
+ storefront_products_for_filters.
31
+ joins(variants_including_master: :option_values).
32
+ group("#{Spree::OptionValue.table_name}.id").
33
+ distinct.
34
+ count
35
+ end
34
36
  end
35
37
 
36
38
  def product_single_filter_aggregations
37
- @product_single_filter_aggregations ||= default_storefront_products_for_filters.
38
- joins(variants_including_master: :option_values).
39
- group("#{Spree::OptionValue.table_name}.id").
40
- distinct.
41
- count
39
+ @product_single_filter_aggregations ||= Rails.cache.fetch(["product-single-filter-aggregations", default_storefront_products_for_filters.cache_key_with_version], expires_in: 1.day) do
40
+ default_storefront_products_for_filters.
41
+ joins(variants_including_master: :option_values).
42
+ group("#{Spree::OptionValue.table_name}.id").
43
+ distinct.
44
+ count
45
+ end
42
46
  end
43
47
 
44
48
  def products_count_for_filter(name, value_id)
@@ -48,23 +52,27 @@ module Spree
48
52
 
49
53
  def filter_price_range
50
54
  @filter_price_range ||= begin
51
- product_ids = products_for_filters_scope(except_filters: [:price]).ids
55
+ products = products_for_filters_scope(except_filters: [:price])
52
56
 
53
- {
54
- min: Spree::Price.for_products(product_ids).minimum(:amount) || 0.to_d,
55
- max: Spree::Price.for_products(product_ids).maximum(:amount) || 0.to_d
56
- }
57
+ Rails.cache.fetch(["price-range", products.cache_key_with_version, current_currency], expires_in: 1.day) do
58
+ {
59
+ min: Spree::Price.for_products(products, current_currency).minimum(:amount) || 0.to_d,
60
+ max: Spree::Price.for_products(products, current_currency).maximum(:amount) || 0.to_d
61
+ }
62
+ end
57
63
  end
58
64
  end
59
65
 
60
66
  def filter_stock_count
61
67
  @filter_stock_count ||= begin
62
- products_scope = products_for_filters_scope(except_filters: [:purchasable, :out_of_stock])
68
+ products = products_for_filters_scope(except_filters: [:purchasable, :out_of_stock])
63
69
 
64
- {
65
- in_stock: products_scope.in_stock.distinct.count,
66
- out_of_stock: products_scope.out_of_stock.distinct.count
67
- }
70
+ Rails.cache.fetch(["stock-count", products.cache_key_with_version], expires_in: 1.day) do
71
+ {
72
+ in_stock: products.in_stock.distinct.count,
73
+ out_of_stock: products.out_of_stock.distinct.count
74
+ }
75
+ end
68
76
  end
69
77
  end
70
78
 
@@ -76,14 +84,18 @@ module Spree
76
84
  if filter_selected
77
85
  default_storefront_filter_values_scope
78
86
  else
79
- Spree::OptionValue.for_products(storefront_products_for_filters).distinct
87
+ all_option_values_scope
80
88
  end
81
89
  end
82
90
 
91
+ def all_option_values_scope
92
+ @all_option_values_scope ||= Spree::OptionValue.for_products(storefront_products_scope).distinct
93
+ end
94
+
83
95
  def filter_values_for_filter(filter)
84
96
  selected = single_option_filter_selected?(filter.name)
85
97
  if filter.option_values.loaded?
86
- storefront_filter_values_ids = storefront_filter_values_scope(selected).pluck(:id)
98
+ storefront_filter_values_ids = storefront_filter_values_scope(selected).ids
87
99
 
88
100
  filter.option_values.find_all { |option_value| storefront_filter_values_ids.include?(option_value.id) }
89
101
  else
@@ -165,7 +165,7 @@ module Spree
165
165
  {
166
166
  '@type' => 'ListItem',
167
167
  'position' => index + 1,
168
- 'url' => spree.product_url(product_slug)
168
+ 'url' => spree.product_url(product_slug, host: current_store.url_or_custom_domain)
169
169
  }
170
170
  end
171
171
  end
@@ -213,5 +213,9 @@ module Spree
213
213
 
214
214
  Spree::ColorsPreviewStylesPresenter.new(option_type.option_values.map { |ov| { name: ov.name, filter_name: ov.name } }).to_s
215
215
  end
216
+
217
+ def product_properties(product)
218
+ product.product_properties.joins(:property).merge(Spree::Property.available_on_front_end).sort_by_property_position
219
+ end
216
220
  end
217
221
  end
@@ -1,6 +1,8 @@
1
1
  module Spree
2
2
  module StorefrontHelper
3
3
  include BaseHelper
4
+ include Spree::ImagesHelper
5
+ include Spree::ShipmentHelper
4
6
  include Heroicon::Engine.helpers
5
7
 
6
8
  # Renders the storefront partials for the given section.
@@ -65,14 +67,15 @@ module Spree
65
67
 
66
68
  def svg_country_icon(country_code)
67
69
  country_code = :us if country_code.to_s.downcase == 'en'
70
+ country_code = :jp if country_code.to_s.downcase == 'ja'
68
71
  tag.span(class: "fi fi-#{country_code.downcase}")
69
72
  end
70
73
 
71
74
  def show_account_pane?
72
75
  !try_spree_current_user &&
73
- (defined?(spree_login_path) && !paths_equal?(canonical_path, spree_login_path)) &&
74
- (defined?(spree_signup_path) && !paths_equal?(canonical_path, spree_signup_path)) &&
75
- (defined?(spree_forgot_password_path) && !paths_equal?(canonical_path, spree_forgot_password_path))
76
+ defined?(spree_login_path) && !paths_equal?(canonical_path, spree_login_path) &&
77
+ defined?(spree_signup_path) && !paths_equal?(canonical_path, spree_signup_path) &&
78
+ defined?(spree_forgot_password_path) && !paths_equal?(canonical_path, spree_forgot_password_path)
76
79
  end
77
80
 
78
81
  def paths_equal?(path1, path2)
@@ -56,6 +56,8 @@ export default class extends Controller {
56
56
  }
57
57
 
58
58
  updateCardIcon(cardType) {
59
+ cardType = (cardType === "mastercard" ? "master" : cardType)
60
+
59
61
  const iconElement = document.getElementById(`credit-card-icon-${cardType}`)
60
62
  if (iconElement) {
61
63
  this.typeContainerTarget.innerHTML = iconElement.innerHTML
@@ -7,4 +7,9 @@ export default class extends Controller {
7
7
  this.containerTarget.classList.add('pointer-events-none', 'opacity-50')
8
8
  this.spinnerTarget.classList.remove('hidden')
9
9
  }
10
+
11
+ enableCart() {
12
+ this.containerTarget.classList.remove('pointer-events-none', 'opacity-50')
13
+ this.spinnerTarget.classList.add('hidden')
14
+ }
10
15
  }
@@ -6,7 +6,7 @@
6
6
  <div class="flex flex-col mb-4">
7
7
  <%= f.label :password, Spree.t(:password) %>
8
8
  <% if @minimum_password_length %>
9
- <em>(<%= @minimum_password_length %> characters minimum)</em>
9
+ <em><%= Spree.t(:minimum_password_length, count: @minimum_password_length) %></em>
10
10
  <% end %>
11
11
  <%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: 'text-input', required: true %>
12
12
  </div>
@@ -10,7 +10,7 @@
10
10
  <div class="flex flex-col mb-4">
11
11
  <%= f.label :password, Spree.t(:password) %>
12
12
  <% if @minimum_password_length %>
13
- <em>(<%= @minimum_password_length %> characters minimum)</em>
13
+ <em><%= Spree.t(:minimum_password_length, count: @minimum_password_length) %></em>
14
14
  <% end %>
15
15
  <%= f.password_field :password, autocomplete: "new-password", class: 'text-input', required: true %>
16
16
  </div>
@@ -9,14 +9,14 @@
9
9
  <%= link_to Spree.t(:forgot_password), new_password_path(resource_name) %>
10
10
  <% end %>
11
11
  <%- if devise_mapping.confirmable? && controller_name != 'user_confirmations' %>
12
- <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
12
+ <%= link_to Spree.t(:didn_t_receive_confirmation_instructions), new_confirmation_path(resource_name) %>
13
13
  <% end %>
14
14
  <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'user_unlocks' %>
15
- <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
15
+ <%= link_to Spree.t(:didn_t_receive_unlock_instructions), new_unlock_path(resource_name) %>
16
16
  <% end %>
17
17
  <%- if devise_mapping.omniauthable? %>
18
18
  <%- resource_class.omniauth_providers.each do |provider| %>
19
- <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
19
+ <%= button_to Spree.t(:sign_in_with_provider, provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
20
20
  <% end %>
21
21
  <% end %>
22
22
  </div>
@@ -42,13 +42,13 @@
42
42
  <%= check_box_tag "default_shipping", "true", @address.user_default_shipping?,
43
43
  class: 'input-checkbox input-address-default',
44
44
  disabled: @address.user_default_shipping? %>
45
- Set as default delivery address
45
+ <%= Spree.t("address_book.set_as_default_delivery_address") %>
46
46
  <% end %>
47
47
  <%= label_tag "default_billing", class: "flex items-center gap-2 text-sm mb-4" do %>
48
48
  <%= check_box_tag "default_billing", "true", @address.user_default_billing?,
49
49
  class: 'input-checkbox input-address-default',
50
50
  disabled: @address.user_default_billing? %>
51
- Set as default billing address
51
+ <%= Spree.t("address_book.set_as_default_billing_address") %>
52
52
  <% end %>
53
53
  </div>
54
54
  <div class="border-t border-default -mx-4 lg:mx-0 px-4 flex justify-end items-center gap-4 py-6 lg:px-8">
@@ -13,20 +13,7 @@
13
13
  </h5>
14
14
  <ul class="select_address list-group mb-5 border mt-3 rounded-md border-default">
15
15
  <% user_available_addresses.each_with_index do |address, idx| %>
16
- <li class="p-0 m-0 border-b border-default" id="<%= [address_type, dom_id(address)].join('_') %>">
17
- <% checked = (address.id == @order.ship_address_id || (!@order.ship_address_id && idx == 0)) %>
18
- <div class="custom-control custom-radio px-5 py-4 flex items-center">
19
- <%= radio_button_tag "order_#{address_name}_id", address.id, checked, class: 'custom-control-input radio-input mt-1 mr-5', data: { action: 'checkout-address-book#select' } %>
20
- <label class="custom-control-label d-block select-none cursor-pointer max-w-[calc(100%-58px)]" for="<%= "order_#{address_name}_id_#{address.id}" %>">
21
- <%= turbo_frame_tag dom_id(address) do %>
22
- <%= render 'spree/shared/address', address: address %>
23
- <div class="address-book-actions mt-3 <% unless checked %>hidden<% end %>">
24
- <%= link_to Spree.t(:edit), spree.edit_address_path(address, checkout: @order.token), class: 'text-dark' %>
25
- </div>
26
- <% end %>
27
- </label>
28
- </div>
29
- </li>
16
+ <%= render 'spree/checkout/address_list_item', address: address, idx: idx, order: @order, address_type: address_type, address_name: address_name %>
30
17
  <% end %>
31
18
  <li class="list-group-item p-0 m-0">
32
19
  <div class="custom-control custom-radio px-5 py-4 flex items-center">
@@ -49,7 +36,7 @@
49
36
  <% end %>
50
37
  </div>
51
38
  </label>
52
- </label>
39
+ </div>
53
40
  </li>
54
41
  </ul>
55
42
  </div>
@@ -58,7 +45,7 @@
58
45
 
59
46
  <%= form_for @order, url: spree.update_checkout_path(@order.token, @order.state), html: { id: "checkout_form_#{@order.state}" } do |form| %>
60
47
  <div>
61
- <%= form.hidden_field :state_lock_version %>
48
+ <%= render 'spree/checkout/order_lock_version', order: @order %>
62
49
  <%= form.hidden_field :ship_address_id, data: { 'checkout-address-book-target': 'addressId' } %>
63
50
  </div>
64
51
 
@@ -77,9 +64,8 @@
77
64
  </div>
78
65
  <% else %>
79
66
  <%= form_for @order, url: spree.update_checkout_path(@order.token, @order.state), html: { id: "checkout_form_#{@order.state}" } do |form| %>
67
+ <%= render 'spree/checkout/order_lock_version', order: @order %>
80
68
  <div class="mb-5">
81
- <%= form.hidden_field :state_lock_version %>
82
-
83
69
  <% if !try_spree_current_user || try_spree_current_user.email.blank? %>
84
70
  <div class="grid grid-cols-1 lg:grid-cols-2 mb-3 font-body">
85
71
  <h5 class="font-body">
@@ -118,34 +104,35 @@
118
104
  <% unless try_spree_current_user %>
119
105
  <% if already_have_an_account? %>
120
106
  <div class="py-2 text-sm">
121
- It seems you already have an account with us. Please
122
- <%= link_to Spree.t(:login), spree_login_path, class: 'text-primary font-semibold' %> to continue.
107
+ <%= Spree.t("storefront.checkout.it_seems_you_already_have_an_account_html", link: link_to(Spree.t(:login), spree_login_path, class: 'text-primary font-semibold')) %>
123
108
  </div>
124
109
  <% end %>
125
110
  <% end %>
126
111
 
127
- <div class="mb-4" data-controller="checkout-address-book" data-checkout-address-book-target="list">
128
- <div id="<%= address_type %>">
129
- <h5 class="mt-5 mb-4 font-body">
130
- <%= Spree.t(address_type + '_address') %>
131
- </h5>
132
- <%= form.fields_for address_name do |address_form| %>
133
- <div class="inner checkout-content-inner" data-hook='<%= "#{address_type}_inner" %>'>
134
- <%= render partial: 'spree/addresses/form', locals: {
135
- address_name: address_name,
136
- address_form: address_form,
137
- address_type: address_type,
138
- address: Spree::Address.new(country: current_store.default_country, user: try_spree_current_user),
139
- form: form
140
- } %>
141
- </div>
142
- <% end %>
112
+ <% if @order.requires_ship_address? %>
113
+ <div class="mb-4" data-controller="checkout-address-book" data-checkout-address-book-target="list">
114
+ <div id="<%= address_type %>">
115
+ <h5 class="mt-5 mb-4 font-body">
116
+ <%= Spree.t(address_type + '_address') %>
117
+ </h5>
118
+ <%= form.fields_for address_name do |address_form| %>
119
+ <div class="inner checkout-content-inner" data-hook='<%= "#{address_type}_inner" %>'>
120
+ <%= render partial: 'spree/addresses/form', locals: {
121
+ address_name: address_name,
122
+ address_form: address_form,
123
+ address_type: address_type,
124
+ address: address_form.object.persisted? ? address_form.object : Spree::Address.new(country: current_store.default_country, user: try_spree_current_user),
125
+ form: form
126
+ } %>
127
+ </div>
128
+ <% end %>
129
+ </div>
143
130
  </div>
144
- </div>
131
+ <%= hidden_field_tag 'save_user_address', true, data: { hook: "save_user_address" } %>
132
+ <% end %>
145
133
 
146
134
  <%= render 'spree/checkout/special_instructions', form: form %>
147
135
 
148
- <%= hidden_field_tag 'save_user_address', true, data: { hook: "save_user_address" } %>
149
136
  </div>
150
137
  <div class="flex justify-end w-full">
151
138
  <%= button_tag Spree.t(:save_and_continue),
@@ -0,0 +1,14 @@
1
+ <li class="p-0 m-0 border-b border-default" id="<%= [address_type, dom_id(address)].join('_') %>">
2
+ <% checked = (address.id == @order.ship_address_id || (!@order.ship_address_id && idx == 0)) %>
3
+ <div class="custom-control custom-radio px-5 py-4 flex items-center">
4
+ <%= radio_button_tag "order_#{address_name}_id", address.id, checked, class: 'custom-control-input radio-input mt-1 mr-5', data: { action: 'checkout-address-book#select' } %>
5
+ <label class="custom-control-label d-block select-none cursor-pointer max-w-[calc(100%-58px)]" for="<%= "order_#{address_name}_id_#{address.id}" %>">
6
+ <%= turbo_frame_tag dom_id(address) do %>
7
+ <%= render 'spree/shared/address', address: address %>
8
+ <div class="address-book-actions mt-3 <% unless checked %>hidden<% end %>">
9
+ <%= link_to Spree.t(:edit), spree.edit_address_path(address, checkout: @order.token), class: 'text-dark' %>
10
+ </div>
11
+ <% end %>
12
+ </label>
13
+ </div>
14
+ </li>
@@ -1,5 +1,5 @@
1
1
  <%= turbo_frame_tag :checkout_coupon_code, class: "shopping-cart-coupon-code #{local_assigns[:classes]} flex flex-col gap-3" do %>
2
- <% if promotion.nil? %>
2
+ <% if promotion.nil? && @order.gift_card.blank? %>
3
3
  <%= form_tag spree.checkout_apply_coupon_code_path(@order.token), method: :patch, data: {controller: "checkout-promotions"} do %>
4
4
  <div class="flex" data-controller="enable-button">
5
5
  <%= hidden_field_tag :promotion_email, @order.email, data: {checkout_promotions_target: 'promotionEmailField'} %>
@@ -8,7 +8,7 @@
8
8
  aria: { label: Spree.t('cart_page.add_promo_code') },
9
9
  data: { 'enable-button-target': 'input' },
10
10
  required: true %>
11
- <%= button_tag 'Apply', class: 'min-h-full ml-3 btn-primary !px-4 !py-3', data: { 'enable-button-target': 'button' }, disabled: true %>
11
+ <%= button_tag Spree.t(:apply), class: 'min-h-full ml-3 btn-primary !px-4 !py-3', data: { 'enable-button-target': 'button' }, disabled: true %>
12
12
  </div>
13
13
  <% end %>
14
14
  <% elsif promotion.present? %>
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  </div>
28
28
  <% end %>
29
- <% elsif @order.respond_to?(:gift_card) && @order.gift_card %>
29
+ <% elsif @order.gift_card.present? %>
30
30
  <%= form_tag spree.checkout_remove_coupon_code_path(@order.token), method: :delete do %>
31
31
  <%= hidden_field_tag :gift_card, @order.gift_card.code %>
32
32
  <div class="m-0">