spree_frontend 4.2.3.1 → 4.3.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/app/assets/config/spree_frontend_manifest.js +4 -0
  4. data/app/assets/images/card-image.svg +4 -0
  5. data/app/assets/images/file-image.svg +4 -0
  6. data/app/assets/images/image.svg +4 -0
  7. data/app/assets/images/noimage/large.png +0 -0
  8. data/app/assets/images/noimage/mini.png +0 -0
  9. data/app/assets/images/noimage/plp.svg +23 -0
  10. data/app/assets/images/noimage/product.png +0 -0
  11. data/app/assets/images/noimage/small.png +0 -0
  12. data/app/assets/javascripts/spree/api/main.js +36 -0
  13. data/app/assets/javascripts/spree/api/storefront/cart.js +49 -0
  14. data/app/assets/javascripts/spree/frontend/checkout/payment.js +18 -5
  15. data/app/assets/javascripts/spree/frontend/checkout.js +16 -0
  16. data/app/assets/javascripts/spree/frontend/currency.js +11 -9
  17. data/app/assets/javascripts/spree/frontend/locale.es6 +13 -0
  18. data/app/assets/javascripts/spree/frontend/views/spree/home/product_carousels.js +3 -3
  19. data/app/assets/javascripts/spree/frontend/views/spree/products/index.js +0 -9
  20. data/app/assets/javascripts/spree/frontend/views/spree/products/price_filters.es6 +46 -0
  21. data/app/assets/javascripts/spree/frontend.js +3 -0
  22. data/app/assets/javascripts/spree.js +78 -0
  23. data/app/assets/stylesheets/spree/frontend/application.scss +1 -0
  24. data/app/assets/stylesheets/spree/frontend/components-bootstrap/button.scss +8 -0
  25. data/app/assets/stylesheets/spree/frontend/helpers/spree/frontend_helper.scss +41 -0
  26. data/app/assets/stylesheets/spree/frontend/variables/variables.scss +2 -2
  27. data/app/assets/stylesheets/spree/frontend/views/spree/checkout/edit.scss +17 -5
  28. data/app/assets/stylesheets/spree/frontend/views/spree/cms_sections/_index.scss +1 -0
  29. data/app/assets/stylesheets/spree/frontend/views/spree/cms_sections/product_buy_bar.scss +20 -0
  30. data/app/assets/stylesheets/spree/frontend/views/spree/home/index.scss +54 -67
  31. data/app/assets/stylesheets/spree/frontend/views/spree/shared/category_nav_bar.scss +3 -3
  32. data/app/assets/stylesheets/spree/frontend/views/spree/shared/footer.scss +4 -0
  33. data/app/assets/stylesheets/spree/frontend/views/spree/shared/main_nav_bar.scss +12 -1
  34. data/app/controllers/spree/addresses_controller.rb +14 -19
  35. data/app/controllers/spree/checkout_controller.rb +5 -3
  36. data/app/controllers/spree/cms_pages_controller.rb +29 -0
  37. data/app/controllers/spree/content_controller.rb +0 -2
  38. data/app/controllers/spree/errors_controller.rb +11 -0
  39. data/app/controllers/spree/home_controller.rb +34 -1
  40. data/app/controllers/spree/locale_controller.rb +7 -1
  41. data/app/controllers/spree/orders_controller.rb +9 -7
  42. data/app/controllers/spree/products_controller.rb +14 -13
  43. data/app/controllers/spree/store_controller.rb +25 -1
  44. data/app/controllers/spree/taxons_controller.rb +12 -5
  45. data/app/helpers/spree/addresses_helper.rb +9 -4
  46. data/app/helpers/spree/cms_pages_helper.rb +23 -0
  47. data/app/helpers/spree/frontend_helper.rb +109 -10
  48. data/app/helpers/spree/navigation_helper.rb +73 -4
  49. data/app/helpers/spree/products_filters_helper.rb +168 -0
  50. data/app/helpers/spree/store_helper.rb +3 -1
  51. data/app/views/spree/checkout/_address.html.erb +1 -1
  52. data/app/views/spree/checkout/_credit_card.html.erb +1 -0
  53. data/app/views/spree/cms_pages/show.html.erb +16 -0
  54. data/app/views/spree/errors/forbidden.html.erb +0 -0
  55. data/app/views/spree/errors/unauthorized.html.erb +0 -0
  56. data/app/views/spree/home/index.html.erb +19 -118
  57. data/app/views/spree/layouts/spree_application.html.erb +2 -2
  58. data/app/views/spree/products/_description.html.erb +16 -19
  59. data/app/views/spree/products/_filters_desktop.html.erb +50 -15
  60. data/app/views/spree/products/_filters_mobile.html.erb +38 -10
  61. data/app/views/spree/products/_sort_desktop.html.erb +17 -11
  62. data/app/views/spree/products/_sort_mobile.html.erb +16 -0
  63. data/app/views/spree/products/index.html.erb +1 -1
  64. data/app/views/spree/shared/_carousel_4_products.html.erb +8 -8
  65. data/app/views/spree/shared/_checkout_header.html.erb +22 -21
  66. data/app/views/spree/shared/_copyright.html.erb +1 -1
  67. data/app/views/spree/shared/_footer.html.erb +18 -12
  68. data/app/views/spree/shared/_head.html.erb +1 -1
  69. data/app/views/spree/shared/_header.html.erb +3 -3
  70. data/app/views/spree/shared/_main_nav_bar.html.erb +61 -43
  71. data/app/views/spree/shared/_mobile_navigation.html.erb +30 -19
  72. data/app/views/spree/shared/_nav_bar.html.erb +1 -1
  73. data/app/views/spree/shared/_option_values.html.erb +2 -21
  74. data/app/views/spree/shared/_order_details.html.erb +1 -1
  75. data/app/views/spree/shared/_paths.html.erb +8 -0
  76. data/app/views/spree/shared/_properties.html.erb +4 -0
  77. data/app/views/spree/shared/_translations.html.erb +2 -1
  78. data/app/views/spree/shared/carousel/_single.html.erb +1 -1
  79. data/app/views/spree/shared/carousel/_thumbnails.html.erb +1 -1
  80. data/app/views/spree/shared/cms/pages/_edit_mode.html.erb +13 -0
  81. data/app/views/spree/shared/cms/sections/_featured_article.html.erb +27 -0
  82. data/app/views/spree/shared/cms/sections/_hero_image.html.erb +60 -0
  83. data/app/views/spree/shared/cms/sections/_image_gallery.html.erb +171 -0
  84. data/app/views/spree/shared/cms/sections/_product_carousel.html.erb +5 -0
  85. data/app/views/spree/shared/cms/sections/_rich_text_content.html.erb +3 -0
  86. data/app/views/spree/shared/cms/sections/_side_by_side_images.html.erb +118 -0
  87. data/app/views/spree/taxons/_header.html.erb +6 -1
  88. data/app/views/spree/users/show.html.erb +4 -1
  89. data/config/initializers/assets.rb +4 -13
  90. data/config/routes.rb +9 -3
  91. data/lib/generators/spree/frontend/install/install_generator.rb +5 -5
  92. data/lib/generators/spree/frontend/install/templates/app/assets/config/manifest.js +2 -0
  93. data/lib/generators/spree/frontend/install/templates/vendor/assets/javascripts/spree/frontend/all.js +8 -0
  94. data/lib/generators/spree/frontend/install/templates/vendor/assets/stylesheets/spree/frontend/all.css +9 -0
  95. data/lib/spree/frontend/engine.rb +6 -0
  96. data/lib/spree/frontend.rb +6 -3
  97. data/spec/fixtures/files/store_favicon.ico +0 -0
  98. data/spree_frontend.gemspec +13 -9
  99. data/vendor/assets/javascripts/cleave.js +8 -0
  100. data/vendor/assets/javascripts/fetch.umd.js +531 -0
  101. data/vendor/assets/javascripts/jquery.payment.js +652 -0
  102. data/vendor/assets/javascripts/jsuri.js +458 -0
  103. data/vendor/assets/javascripts/polyfill.min.js +1 -0
  104. metadata +132 -86
  105. data/app/assets/images/homepage/big_category_banner.jpg +0 -0
  106. data/app/assets/images/homepage/big_category_banner_mobile.jpg +0 -0
  107. data/app/assets/images/homepage/big_category_banner_tablet_landscape.jpg +0 -0
  108. data/app/assets/images/homepage/big_category_banner_tablet_portrait.jpg +0 -0
  109. data/app/assets/images/homepage/category_banner_lower.jpg +0 -0
  110. data/app/assets/images/homepage/category_banner_lower_mobile.jpg +0 -0
  111. data/app/assets/images/homepage/category_banner_lower_tablet_landscape.jpg +0 -0
  112. data/app/assets/images/homepage/category_banner_lower_tablet_portrait.jpg +0 -0
  113. data/app/assets/images/homepage/category_banner_upper.jpg +0 -0
  114. data/app/assets/images/homepage/category_banner_upper_mobile.jpg +0 -0
  115. data/app/assets/images/homepage/category_banner_upper_tablet_landscape.jpg +0 -0
  116. data/app/assets/images/homepage/category_banner_upper_tablet_portrait.jpg +0 -0
  117. data/app/assets/images/homepage/main_banner.jpg +0 -0
  118. data/app/assets/images/homepage/main_banner_mobile.jpg +0 -0
  119. data/app/assets/images/homepage/main_banner_tablet_landscape.jpg +0 -0
  120. data/app/assets/images/homepage/main_banner_tablet_portrait.jpg +0 -0
  121. data/app/assets/images/homepage/promo_banner_left.jpg +0 -0
  122. data/app/assets/images/homepage/promo_banner_left_mobile.jpg +0 -0
  123. data/app/assets/images/homepage/promo_banner_left_tablet_landscape.jpg +0 -0
  124. data/app/assets/images/homepage/promo_banner_left_tablet_portrait.jpg +0 -0
  125. data/app/assets/images/homepage/promo_banner_right.jpg +0 -0
  126. data/app/assets/images/homepage/promo_banner_right_mobile.jpg +0 -0
  127. data/app/assets/images/homepage/promo_banner_right_tablet_landscape.jpg +0 -0
  128. data/app/assets/images/homepage/promo_banner_right_tablet_portrait.jpg +0 -0
  129. data/app/assets/images/meganav/promo_banner_left-first-category.jpg +0 -0
  130. data/app/assets/images/meganav/promo_banner_left-second-category.jpg +0 -0
  131. data/app/assets/images/meganav/promo_banner_left-third-category.jpg +0 -0
  132. data/app/assets/images/meganav/promo_banner_right-first-category.jpg +0 -0
  133. data/app/assets/images/meganav/promo_banner_right-second-category.jpg +0 -0
  134. data/app/assets/images/meganav/promo_banner_right-third-category.jpg +0 -0
  135. data/app/assets/images/noimage/big_category_banner.jpg +0 -0
  136. data/app/assets/images/noimage/big_category_banner_mobile.jpg +0 -0
  137. data/app/assets/images/noimage/big_category_banner_tablet_landscape.jpg +0 -0
  138. data/app/assets/images/noimage/big_category_banner_tablet_portrait.jpg +0 -0
  139. data/app/assets/images/noimage/category_banner.jpg +0 -0
  140. data/app/assets/images/noimage/category_banner_mobile.jpg +0 -0
  141. data/app/assets/images/noimage/category_banner_plp.jpg +0 -0
  142. data/app/assets/images/noimage/category_banner_tablet_landscape.jpg +0 -0
  143. data/app/assets/images/noimage/category_banner_tablet_portrait.jpg +0 -0
  144. data/app/assets/images/noimage/main_banner.jpg +0 -0
  145. data/app/assets/images/noimage/main_banner_mobile.jpg +0 -0
  146. data/app/assets/images/noimage/main_banner_tablet_landscape.jpg +0 -0
  147. data/app/assets/images/noimage/main_banner_tablet_portrait.jpg +0 -0
  148. data/app/assets/images/noimage/product_photo.jpg +0 -0
  149. data/app/assets/images/noimage/promo_banner.jpg +0 -0
  150. data/app/assets/images/noimage/promo_banner_megamenu.jpg +0 -0
  151. data/app/assets/images/noimage/promo_banner_mobile.jpg +0 -0
  152. data/app/assets/images/noimage/promo_banner_tablet_landscape.jpg +0 -0
  153. data/app/assets/images/noimage/promo_banner_tablet_portrait.jpg +0 -0
  154. data/app/assets/javascripts/spree/frontend/locale.js +0 -13
  155. data/app/assets/javascripts/spree/frontend/multi_currency.js +0 -13
  156. data/lib/generators/spree/frontend/install/templates/config/initializers/spree_storefront.rb +0 -1
  157. data/lib/generators/spree/frontend/install/templates/config/spree_storefront.yml +0 -99
@@ -1,5 +1,6 @@
1
1
  module Spree
2
2
  module FrontendHelper
3
+ include BaseHelper
3
4
  include InlineSvg::ActionView::Helpers
4
5
 
5
6
  def body_class
@@ -7,6 +8,22 @@ module Spree
7
8
  @body_class
8
9
  end
9
10
 
11
+ def logo(image_path = nil, options = {})
12
+ image_path ||= if current_store.logo.attached? && current_store.logo.variable?
13
+ main_app.url_for(current_store.logo.variant(resize: '244x104>'))
14
+ elsif current_store.logo.attached? && current_store.logo.image?
15
+ main_app.url_for(current_store.logo)
16
+ else
17
+ Spree::Config[:logo]
18
+ end
19
+
20
+ path = spree.respond_to?(:root_path) ? spree.root_path : main_app.root_path
21
+
22
+ link_to path, 'aria-label': current_store.name, method: options[:method] do
23
+ image_tag image_path, alt: current_store.name, title: current_store.name
24
+ end
25
+ end
26
+
10
27
  def spree_breadcrumbs(taxon, _separator = '', product = nil)
11
28
  return '' if current_page?('/') || taxon.nil?
12
29
 
@@ -137,10 +154,10 @@ module Spree
137
154
  end
138
155
 
139
156
  def asset_exists?(path)
140
- if Rails.env.production?
141
- Rails.application.assets_manifest.find_sources(path).present?
157
+ if Rails.configuration.assets.compile
158
+ Rails.application.precompiled_assets.include? path
142
159
  else
143
- Rails.application.assets.find_asset(path).present?
160
+ Rails.application.assets_manifest.assets[path].present?
144
161
  end
145
162
  end
146
163
 
@@ -150,7 +167,7 @@ module Spree
150
167
  image_url = if image.present?
151
168
  main_app.url_for(image.url('plp'))
152
169
  else
153
- asset_path('noimage/plp.png')
170
+ asset_path('noimage/plp.svg')
154
171
  end
155
172
 
156
173
  image_style = image&.style(:plp)
@@ -228,6 +245,11 @@ module Spree
228
245
  end
229
246
 
230
247
  def price_filter_values
248
+ ActiveSupport::Deprecation.warn(<<-DEPRECATION, caller)
249
+ `FrontendHelper#price_filter_values` is deprecated and will be removed in Spree 5.0.
250
+ Please use `ProductsFiltersHelper#price_filters` method
251
+ DEPRECATION
252
+
231
253
  @price_filter_values ||= [
232
254
  "#{I18n.t('activerecord.attributes.spree/product.less_than')} #{formatted_price(50)}",
233
255
  "#{formatted_price(50)} - #{formatted_price(100)}",
@@ -250,18 +272,81 @@ module Spree
250
272
  end
251
273
 
252
274
  def filtering_params_cache_key
253
- @filtering_params_cache_key ||= params.permit(*filtering_params)&.reject { |_, v| v.blank? }&.to_param
275
+ @filtering_params_cache_key ||= begin
276
+ cache_key_parts = []
277
+
278
+ permitted_products_params.each do |key, value|
279
+ next if value.blank?
280
+
281
+ if value.is_a?(String)
282
+ cache_key_parts << [key, value].join('-')
283
+ else
284
+ value.each do |part_key, part_value|
285
+ next if part_value.blank?
286
+
287
+ cache_key_parts << [part_key, part_value].join('-')
288
+ end
289
+ end
290
+ end
291
+
292
+ cache_key_parts.join('-').parameterize
293
+ end
294
+ end
295
+
296
+ def filters_cache_key(kind)
297
+ base_cache_key + [
298
+ kind,
299
+ available_option_types_cache_key,
300
+ available_properties_cache_key,
301
+ filtering_params_cache_key,
302
+ @taxon&.id,
303
+ params[:menu_open]
304
+ ].flatten
305
+ end
306
+
307
+ def permitted_products_params
308
+ @permitted_products_params ||= begin
309
+ params.permit(*filtering_params, properties: available_properties.map(&:filter_param))
310
+ end
311
+ end
312
+
313
+ def option_type_cache_key(option_type)
314
+ filter_param = option_type.filter_param
315
+ filtered_params = params[filter_param]
316
+
317
+ [
318
+ available_option_types_cache_key,
319
+ filter_param,
320
+ filtered_params
321
+ ]
254
322
  end
255
323
 
256
324
  def available_option_types_cache_key
257
- @available_option_types_cache_key ||= Spree::OptionType.filterable.maximum(:updated_at)&.utc&.to_i
325
+ @available_option_types_cache_key ||= [
326
+ Spree::OptionType.filterable.maximum(:updated_at).to_f,
327
+ products_for_filters_cache_key
328
+ ].flatten.join('/')
258
329
  end
259
330
 
260
331
  def available_option_types
261
332
  @available_option_types ||= Rails.cache.fetch("available-option-types/#{available_option_types_cache_key}") do
262
- Spree::OptionType.includes(:option_values).filterable.to_a
333
+ option_values = OptionValues::FindAvailable.new(products_scope: products_for_filters).execute
334
+ Filters::OptionsPresenter.new(option_values_scope: option_values).to_a
335
+ end
336
+ end
337
+
338
+ def available_properties_cache_key
339
+ @available_properties_cache_key ||= [
340
+ Spree::Property.filterable.maximum(:updated_at).to_f,
341
+ products_for_filters_cache_key
342
+ ].flatten.join('/')
343
+ end
344
+
345
+ def available_properties
346
+ @available_properties ||= Rails.cache.fetch("available-properties/#{available_properties_cache_key}") do
347
+ product_properties = ProductProperties::FindAvailable.new(products_scope: products_for_filters).execute
348
+ Filters::PropertiesPresenter.new(product_properties_scope: product_properties).to_a
263
349
  end
264
- @available_option_types
265
350
  end
266
351
 
267
352
  def spree_social_link(service)
@@ -275,7 +360,7 @@ module Spree
275
360
  end
276
361
 
277
362
  def checkout_available_payment_methods
278
- @checkout_available_payment_methods ||= @order.available_payment_methods(current_store)
363
+ @checkout_available_payment_methods ||= @order.available_payment_methods
279
364
  end
280
365
 
281
366
  def color_option_type_name
@@ -305,7 +390,7 @@ module Spree
305
390
  end
306
391
 
307
392
  def checkout_edit_link(step = 'address', order = @order)
308
- return if order.complete?
393
+ return if order.uneditable?
309
394
 
310
395
  classes = 'align-text-bottom checkout-confirm-delivery-informations-link'
311
396
 
@@ -313,5 +398,19 @@ module Spree
313
398
  inline_svg_tag 'edit.svg'
314
399
  end
315
400
  end
401
+
402
+ def products_for_filters
403
+ @products_for_filters ||= current_store.products.for_filters(current_currency, taxon: @taxon)
404
+ end
405
+
406
+ def products_for_filters_cache_key
407
+ @products_for_filters_cache_key ||= begin
408
+ [
409
+ products_for_filters.maximum(:updated_at).to_f,
410
+ base_cache_key,
411
+ @taxon&.permalink
412
+ ].flatten.compact
413
+ end
414
+ end
316
415
  end
317
416
  end
@@ -3,22 +3,46 @@ require 'digest'
3
3
  module Spree
4
4
  module NavigationHelper
5
5
  def spree_navigation_data
6
- @spree_navigation_data ||= SpreeStorefrontConfig.dig(I18n.locale, :navigation) || SpreeStorefrontConfig.dig(current_store.code, :navigation) || SpreeStorefrontConfig.dig(:default, :navigation) || []
6
+ ActiveSupport::Deprecation.warn(<<-DEPRECATION, caller)
7
+ NavigationHelper#spree_navigation_data is deprecated and will be removed in Spree 5.0.
8
+ Please migrate to the new navigation cms system.
9
+ DEPRECATION
10
+
11
+ @spree_navigation_data ||= SpreeStorefrontConfig.dig(I18n.locale,
12
+ :navigation) || SpreeStorefrontConfig.dig(current_store.code,
13
+ :navigation) || SpreeStorefrontConfig.dig(
14
+ :default, :navigation
15
+ ) || []
7
16
  # safeguard for older Spree installs that don't have spree_navigation initializer
8
17
  # or spree.yml file present
9
- rescue
18
+ rescue StandardError
10
19
  []
11
20
  end
12
21
 
13
22
  def spree_nav_cache_key(section = 'header')
23
+ ActiveSupport::Deprecation.warn(<<-DEPRECATION, caller)
24
+ NavigationHelper#spree_nav_cache_key is deprecated and will be removed in Spree 5.0.
25
+ Please migrate to the new navigation cms system.
26
+ DEPRECATION
27
+
14
28
  @spree_nav_cache_key = begin
15
- keys = base_cache_key + [current_store, spree_navigation_data_cache_key, Spree::Config[:logo], stores&.cache_key, section]
29
+ keys = base_cache_key + [current_store, spree_navigation_data_cache_key, Spree::Config[:logo], stores&.cache_key_with_version, section]
16
30
  Digest::MD5.hexdigest(keys.join('-'))
17
31
  end
18
32
  end
19
33
 
34
+ def spree_menu_cache_key(section = 'header')
35
+ keys = base_cache_key + [
36
+ current_store.cache_key_with_version,
37
+ spree_menu(section)&.cache_key_with_version,
38
+ stores&.maximum(:updated_at),
39
+ section
40
+ ]
41
+ Digest::MD5.hexdigest(keys.join('-'))
42
+ end
43
+
20
44
  def main_nav_image(image_path, title = '')
21
- image_url = asset_path(asset_exists?(image_path) ? image_path : 'noimage/plp.png')
45
+ image_url = asset_path(asset_exists?(image_path) ? image_path : 'noimage/plp.svg')
22
46
 
23
47
  lazy_image(
24
48
  src: image_url,
@@ -28,11 +52,56 @@ module Spree
28
52
  )
29
53
  end
30
54
 
55
+ def spree_menu(location = 'header')
56
+ method_name = "for_#{location}"
57
+
58
+ if available_menus.respond_to?(method_name) && Spree::Menu::MENU_LOCATIONS_PARAMETERIZED.include?(location)
59
+ available_menus.send(method_name, I18n.locale) || current_store.default_menu(location)
60
+ end
61
+ end
62
+
63
+ def spree_localized_link(item)
64
+ return if item.link.nil?
65
+
66
+ output_locale = if locale_param
67
+ "/#{I18n.locale}"
68
+ end
69
+
70
+ if Spree::MenuItem::DYNAMIC_RESOURCE_TYPE.include? item.linked_resource_type
71
+ output_locale.to_s + item.link
72
+ elsif item.linked_resource_type == 'Home Page'
73
+ "/#{locale_param}"
74
+ else
75
+ item.link
76
+ end
77
+ end
78
+
31
79
  def should_render_internationalization_dropdown?
32
80
  (defined?(should_render_locale_dropdown?) && should_render_locale_dropdown?) ||
33
81
  (defined?(should_render_currency_dropdown?) && should_render_currency_dropdown?)
34
82
  end
35
83
 
84
+ def spree_nav_link_tag(item, opts = {}, &block)
85
+ if item.new_window
86
+ target = opts[:target] || '_blank'
87
+ rel = opts[:rel] || 'noopener noreferrer'
88
+ end
89
+
90
+ active_class = if request && current_page?(spree_localized_link(item))
91
+ "active #{opts[:class]}"
92
+ else
93
+ opts[:class]
94
+ end
95
+
96
+ link_opts = { target: target, rel: rel, class: active_class, id: opts[:id], data: opts[:data], aria: opts[:aria] }
97
+
98
+ if block_given?
99
+ link_to spree_localized_link(item), link_opts, &block
100
+ else
101
+ link_to item.name, spree_localized_link(item), link_opts
102
+ end
103
+ end
104
+
36
105
  private
37
106
 
38
107
  def spree_navigation_data_cache_key
@@ -0,0 +1,168 @@
1
+ module Spree
2
+ module ProductsFiltersHelper
3
+ PRICE_FILTER_NAME = 'price'.freeze
4
+
5
+ FILTER_LINK_CSS_CLASSES = 'd-inline-block text-uppercase py-1 px-2 m-1 plp-overlay-card-item'.freeze
6
+ ACTIVE_FILTER_LINK_CSS_CLASSES = 'plp-overlay-card-item--selected'.freeze
7
+
8
+ CLEAR_ALL_FILTERS_LINK_CSS_CLASSES = 'btn spree-btn btn-outline-primary w-100 mb-4'.freeze
9
+
10
+ def price_filters
11
+ @price_filters ||= [
12
+ filters_less_than_price_range(50),
13
+ filters_price_range(50, 100),
14
+ filters_price_range(101, 150),
15
+ filters_price_range(151, 200),
16
+ filters_price_range(201, 300),
17
+ filters_more_than_price_range(300)
18
+ ]
19
+ end
20
+
21
+ def min_price_filter_input(**html_options)
22
+ price_filter_input(
23
+ name: :min_price,
24
+ value: filters_price_range_from_param.min_price.to_i,
25
+ placeholder: "#{currency_symbol(current_currency)} #{Spree.t(:min)}",
26
+ **html_options
27
+ )
28
+ end
29
+
30
+ def max_price_filter_input(**html_options)
31
+ price_filter_input(
32
+ name: :max_price,
33
+ value: filters_price_range_from_param.max_price.to_i,
34
+ placeholder: "#{currency_symbol(current_currency)} #{Spree.t(:max)}",
35
+ **html_options
36
+ )
37
+ end
38
+
39
+ def price_filter_input(name:, value:, placeholder:, **html_options)
40
+ price_value = value&.zero? ? '' : value
41
+ style_class = "spree-flat-input #{html_options[:class]}"
42
+
43
+ number_field_tag(
44
+ name, price_value,
45
+ id: name,
46
+ class: style_class,
47
+ min: 0, step: 1, placeholder: placeholder,
48
+ **html_options.except(:class)
49
+ )
50
+ end
51
+
52
+ def option_value_filter_link(option_value, permitted_params, opts = {})
53
+ id = option_value.id
54
+ ot_downcase_name = option_value.option_type.filter_param
55
+ selected_option_values = params[ot_downcase_name]&.split(',')&.map(&:to_i) || []
56
+ is_selected = selected_option_values.include?(id)
57
+ option_value_param = (is_selected ? selected_option_values - [id] : selected_option_values + [id]).join(',')
58
+ new_params = permitted_params.merge(ot_downcase_name => option_value_param, menu_open: 1)
59
+
60
+ link_to new_params, data: { params: new_params, id: id, filter_name: ot_downcase_name, multiselect: true } do
61
+ # TODO: refactor this
62
+ if color_option_type_name.present? && color_option_type_name.downcase == ot_downcase_name
63
+ content_tag :span, class: 'd-inline-block mb-1' do
64
+ render partial: 'spree/shared/color_select', locals: {
65
+ color: option_value.presentation,
66
+ selected: is_selected
67
+ }
68
+ end
69
+ else
70
+ filter_content_tag(option_value.name, opts.merge(is_selected: is_selected))
71
+ end
72
+ end
73
+ end
74
+
75
+ def property_value_filter_link(property, value, permitted_params, opts = {})
76
+ id = value.first
77
+ name = value.last
78
+
79
+ selected_properties = params.dig(:properties, property.filter_param)&.split(',') || []
80
+ is_selected = selected_properties.include?(id)
81
+ property_values = (is_selected ? selected_properties - [id] : selected_properties + [id])
82
+ url = permitted_params.merge(properties: { property.filter_param => property_values }, menu_open: 1)
83
+ filter_name = "properties[#{property.filter_param}]"
84
+ new_params = permitted_params.merge(filter_name => property_values, menu_open: 1)
85
+
86
+ base_filter_link(url, name, opts.merge(params: new_params, is_selected: is_selected, filter_name: filter_name, id: id, multiselect: true))
87
+ end
88
+
89
+ def price_filter_link(price_range, permitted_params, opts = {})
90
+ is_selected = params[:price] == price_range.to_param
91
+ price_param = is_selected ? '' : price_range.to_param
92
+ url = permitted_params.merge(price: price_param, menu_open: 1)
93
+
94
+ opts[:is_selected] ||= is_selected
95
+
96
+ content_tag :div do
97
+ base_filter_link(url, price_range, opts.merge(is_selected: is_selected, filter_name: PRICE_FILTER_NAME, id: price_param))
98
+ end
99
+ end
100
+
101
+ def product_filters_present?(permitted_params)
102
+ properties = permitted_params.fetch(:properties, {}).to_unsafe_h
103
+ flatten_params = permitted_params.to_h.merge(properties)
104
+
105
+ flatten_params.any? { |name, value| product_filter_present?(name, value) }
106
+ end
107
+
108
+ def clear_all_filters_link(permitted_params, opts = {})
109
+ opts[:css_class] ||= CLEAR_ALL_FILTERS_LINK_CSS_CLASSES
110
+ sort_by_param = permitted_params.slice(:sort_by)
111
+
112
+ link_to Spree.t('plp.clear_all'), sort_by_param, class: opts[:css_class], data: { params: sort_by_param }
113
+ end
114
+
115
+ private
116
+
117
+ def product_filter_present?(filter_param, filter_value)
118
+ filter_param.in?(product_filters_params) && filter_value.present?
119
+ end
120
+
121
+ def product_filters_params
122
+ options_filter_params = OptionType.filterable.map(&:filter_param)
123
+ properties_filter_params = Property.filterable.pluck(:filter_param)
124
+
125
+ options_filter_params + properties_filter_params + [PRICE_FILTER_NAME]
126
+ end
127
+
128
+ def base_filter_link(url, label, opts = {})
129
+ opts[:params] ||= url
130
+
131
+ link_to url, data: { filter_name: opts[:filter_name], id: opts[:id], params: opts[:params], multiselect: opts[:multiselect] } do
132
+ filter_content_tag(label, opts)
133
+ end
134
+ end
135
+
136
+ def filter_content_tag(label, opts = {})
137
+ opts[:css_class] ||= FILTER_LINK_CSS_CLASSES
138
+ opts[:css_active_class] ||= ACTIVE_FILTER_LINK_CSS_CLASSES
139
+
140
+ content_tag :div, class: "#{opts[:css_class]} #{opts[:css_active_class] if opts[:is_selected]}" do
141
+ label.to_s
142
+ end
143
+ end
144
+
145
+ def filters_price_range_from_param
146
+ Filters::PriceRangePresenter.from_param(params[:price].to_s, currency: current_currency)
147
+ end
148
+
149
+ def filters_price_range(min_amount, max_amount)
150
+ Filters::PriceRangePresenter.new(
151
+ min_price: filters_price(min_amount),
152
+ max_price: filters_price(max_amount)
153
+ )
154
+ end
155
+
156
+ def filters_less_than_price_range(amount)
157
+ Filters::QuantifiedPriceRangePresenter.new(price: filters_price(amount), quantifier: :less_than)
158
+ end
159
+
160
+ def filters_more_than_price_range(amount)
161
+ Filters::QuantifiedPriceRangePresenter.new(price: filters_price(amount), quantifier: :more_than)
162
+ end
163
+
164
+ def filters_price(amount)
165
+ Filters::PricePresenter.new(amount: amount, currency: current_currency)
166
+ end
167
+ end
168
+ end