spree_storefront 5.0.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 (341) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +13 -0
  3. data/Rakefile +15 -0
  4. data/app/assets/config/spree_storefront_manifest.js +6 -0
  5. data/app/assets/stylesheets/storefront_page_builder.css +51 -0
  6. data/app/controllers/concerns/spree/cart_methods.rb +40 -0
  7. data/app/controllers/concerns/spree/locale_urls.rb +23 -0
  8. data/app/controllers/concerns/spree/password_protected.rb +17 -0
  9. data/app/controllers/concerns/spree/theme_concern.rb +29 -0
  10. data/app/controllers/spree/account/addresses_controller.rb +16 -0
  11. data/app/controllers/spree/account/base_controller.rb +20 -0
  12. data/app/controllers/spree/account/newsletter_controller.rb +34 -0
  13. data/app/controllers/spree/account/orders_controller.rb +34 -0
  14. data/app/controllers/spree/account/profile_controller.rb +23 -0
  15. data/app/controllers/spree/account/store_credits_controller.rb +23 -0
  16. data/app/controllers/spree/account/wished_items_controller.rb +45 -0
  17. data/app/controllers/spree/addresses_controller.rb +101 -0
  18. data/app/controllers/spree/checkout_controller.rb +365 -0
  19. data/app/controllers/spree/contacts_controller.rb +46 -0
  20. data/app/controllers/spree/digital_links_controller.rb +48 -0
  21. data/app/controllers/spree/home_controller.rb +5 -0
  22. data/app/controllers/spree/line_items_controller.rb +96 -0
  23. data/app/controllers/spree/newsletter_subscribers_controller.rb +43 -0
  24. data/app/controllers/spree/order_status_controller.rb +32 -0
  25. data/app/controllers/spree/orders_controller.rb +104 -0
  26. data/app/controllers/spree/page_sections_controller.rb +17 -0
  27. data/app/controllers/spree/pages_controller.rb +13 -0
  28. data/app/controllers/spree/password_controller.rb +21 -0
  29. data/app/controllers/spree/policies_controller.rb +17 -0
  30. data/app/controllers/spree/posts_controller.rb +76 -0
  31. data/app/controllers/spree/products_controller.rb +99 -0
  32. data/app/controllers/spree/search_controller.rb +39 -0
  33. data/app/controllers/spree/seo_controller.rb +25 -0
  34. data/app/controllers/spree/settings_controller.rb +68 -0
  35. data/app/controllers/spree/store_controller.rb +247 -0
  36. data/app/controllers/spree/taxonomies_controller.rb +30 -0
  37. data/app/controllers/spree/taxons_controller.rb +46 -0
  38. data/app/controllers/spree/wishlists_controller.rb +19 -0
  39. data/app/finders/spree/storefront/variant_finder.rb +80 -0
  40. data/app/helpers/spree/analytics_helper.rb +28 -0
  41. data/app/helpers/spree/cart_helper.rb +48 -0
  42. data/app/helpers/spree/checkout_analytics_helper.rb +77 -0
  43. data/app/helpers/spree/checkout_helper.rb +58 -0
  44. data/app/helpers/spree/filters_helper.rb +180 -0
  45. data/app/helpers/spree/fonts_helper.rb +58 -0
  46. data/app/helpers/spree/orders_helper.rb +7 -0
  47. data/app/helpers/spree/page_helper.rb +67 -0
  48. data/app/helpers/spree/posts_helper.rb +42 -0
  49. data/app/helpers/spree/products_helper.rb +205 -0
  50. data/app/helpers/spree/storefront_helper.rb +86 -0
  51. data/app/helpers/spree/storefront_locale_helper.rb +42 -0
  52. data/app/helpers/spree/theme_helper.rb +244 -0
  53. data/app/helpers/spree/turbo_helper.rb +13 -0
  54. data/app/helpers/spree/turbo_stream_actions_helper.rb +13 -0
  55. data/app/helpers/spree/wishlist_helper.rb +7 -0
  56. data/app/javascript/spree/storefront/application.js +156 -0
  57. data/app/javascript/spree/storefront/controllers/accordion_controller.js +25 -0
  58. data/app/javascript/spree/storefront/controllers/account_nav_controller.js +10 -0
  59. data/app/javascript/spree/storefront/controllers/card_validation_controller.js +103 -0
  60. data/app/javascript/spree/storefront/controllers/carousel_controller.js +44 -0
  61. data/app/javascript/spree/storefront/controllers/cart_controller.js +10 -0
  62. data/app/javascript/spree/storefront/controllers/checkout_address_book_controller.js +28 -0
  63. data/app/javascript/spree/storefront/controllers/checkout_delivery_controller.js +39 -0
  64. data/app/javascript/spree/storefront/controllers/checkout_promotions_controller.js +28 -0
  65. data/app/javascript/spree/storefront/controllers/checkout_summary_controller.js +46 -0
  66. data/app/javascript/spree/storefront/controllers/clear_input_controller.js +23 -0
  67. data/app/javascript/spree/storefront/controllers/copy_input_controller.js +19 -0
  68. data/app/javascript/spree/storefront/controllers/dropdown_controller.js +14 -0
  69. data/app/javascript/spree/storefront/controllers/header_controller.js +33 -0
  70. data/app/javascript/spree/storefront/controllers/infinite_scroll_controller.js +31 -0
  71. data/app/javascript/spree/storefront/controllers/lightbox_controller.js +138 -0
  72. data/app/javascript/spree/storefront/controllers/mobile_nav_controller.js +17 -0
  73. data/app/javascript/spree/storefront/controllers/modal_controller.js +15 -0
  74. data/app/javascript/spree/storefront/controllers/no_ui_slider_controller.js +55 -0
  75. data/app/javascript/spree/storefront/controllers/pdp_desktop_gallery_controller.js +28 -0
  76. data/app/javascript/spree/storefront/controllers/plp_variant_picker_controller.js +151 -0
  77. data/app/javascript/spree/storefront/controllers/product_form_controller.js +136 -0
  78. data/app/javascript/spree/storefront/controllers/quantity_picker_controller.js +43 -0
  79. data/app/javascript/spree/storefront/controllers/search_suggestions_controller.js +61 -0
  80. data/app/javascript/spree/storefront/controllers/searchable_list_controller.js +25 -0
  81. data/app/javascript/spree/storefront/controllers/slideover_controller.js +40 -0
  82. data/app/javascript/spree/storefront/controllers/sticky_button_controller.js +32 -0
  83. data/app/javascript/spree/storefront/controllers/toggle_menu_controller.js +32 -0
  84. data/app/javascript/spree/storefront/controllers/turbo_stream_form_controller.js +51 -0
  85. data/app/javascript/spree/storefront/controllers/wished_item_controller.js +69 -0
  86. data/app/javascript/spree/storefront/helpers/lazy_load_controllers_with_manifest.js +78 -0
  87. data/app/javascript/spree/storefront/helpers/show_flash_message.js +25 -0
  88. data/app/models/spree/color_names.rb +35 -0
  89. data/app/models/spree/contact.rb +24 -0
  90. data/app/presenters/spree/colors_preview_styles_presenter.rb +84 -0
  91. data/app/presenters/spree/featured_product_presenter.rb +42 -0
  92. data/app/presenters/spree/mega_nav_presenter.rb +55 -0
  93. data/app/views/devise/passwords/edit.html.erb +22 -0
  94. data/app/views/devise/passwords/new.html.erb +16 -0
  95. data/app/views/devise/registrations/_form.html.erb +21 -0
  96. data/app/views/devise/registrations/edit.html.erb +24 -0
  97. data/app/views/devise/registrations/new.html.erb +27 -0
  98. data/app/views/devise/sessions/new.html.erb +25 -0
  99. data/app/views/devise/shared/_links.html.erb +22 -0
  100. data/app/views/layouts/spree/checkout.html.erb +58 -0
  101. data/app/views/layouts/spree/password.html.erb +34 -0
  102. data/app/views/layouts/spree/storefront.html.erb +38 -0
  103. data/app/views/spree/account/wished_items/create.turbo_stream.erb +8 -0
  104. data/app/views/spree/account/wished_items/destroy.turbo_stream.erb +20 -0
  105. data/app/views/spree/addresses/destroy.turbo_stream.erb +1 -0
  106. data/app/views/spree/addresses/edit.html.erb +57 -0
  107. data/app/views/spree/addresses/new.html.erb +71 -0
  108. data/app/views/spree/checkout/_address.html.erb +153 -0
  109. data/app/views/spree/checkout/_button_processing.html.erb +7 -0
  110. data/app/views/spree/checkout/_coupon_code.html.erb +55 -0
  111. data/app/views/spree/checkout/_credit_card.html.erb +12 -0
  112. data/app/views/spree/checkout/_delivery.html.erb +70 -0
  113. data/app/views/spree/checkout/_line_item.html.erb +26 -0
  114. data/app/views/spree/checkout/_line_items.html.erb +7 -0
  115. data/app/views/spree/checkout/_missing_all_line_items.html.erb +17 -0
  116. data/app/views/spree/checkout/_missing_line_items.html.erb +28 -0
  117. data/app/views/spree/checkout/_payment.html.erb +35 -0
  118. data/app/views/spree/checkout/_payment_methods.html.erb +72 -0
  119. data/app/views/spree/checkout/_payment_sources.html.erb +24 -0
  120. data/app/views/spree/checkout/_quick_checkout.html.erb +1 -0
  121. data/app/views/spree/checkout/_sidebar.html.erb +34 -0
  122. data/app/views/spree/checkout/_store_credit.html.erb +21 -0
  123. data/app/views/spree/checkout/_summary.html.erb +116 -0
  124. data/app/views/spree/checkout/_user_account.html.erb +9 -0
  125. data/app/views/spree/checkout/apply_coupon_code.turbo_stream.erb +45 -0
  126. data/app/views/spree/checkout/apply_store_credit.turbo_stream.erb +19 -0
  127. data/app/views/spree/checkout/edit.html.erb +98 -0
  128. data/app/views/spree/checkout/payment/_check.html.erb +0 -0
  129. data/app/views/spree/checkout/payment/_gateway.html.erb +74 -0
  130. data/app/views/spree/checkout/payment/_store_credit.html.erb +13 -0
  131. data/app/views/spree/checkout/remove_coupon_code.turbo_stream.erb +34 -0
  132. data/app/views/spree/checkout/remove_store_credit.turbo_stream.erb +19 -0
  133. data/app/views/spree/checkout/update.turbo_stream.erb +25 -0
  134. data/app/views/spree/home/index.html.erb +1 -0
  135. data/app/views/spree/line_items/create.turbo_stream.erb +13 -0
  136. data/app/views/spree/line_items/destroy.turbo_stream.erb +11 -0
  137. data/app/views/spree/line_items/update.turbo_stream.erb +10 -0
  138. data/app/views/spree/newsletter_subscribers/create.turbo_stream.erb +7 -0
  139. data/app/views/spree/order_status/new.html.erb +16 -0
  140. data/app/views/spree/page_sections/show.html.erb +3 -0
  141. data/app/views/spree/pages/show.html.erb +1 -0
  142. data/app/views/spree/password/show.html.erb +1 -0
  143. data/app/views/spree/posts/index.html.erb +1 -0
  144. data/app/views/spree/posts/related_products.html.erb +7 -0
  145. data/app/views/spree/posts/show.html.erb +1 -0
  146. data/app/views/spree/products/index.html.erb +1 -0
  147. data/app/views/spree/products/index.turbo_stream.erb +1 -0
  148. data/app/views/spree/products/related.html.erb +3 -0
  149. data/app/views/spree/products/show.html.erb +1 -0
  150. data/app/views/spree/search/show.html.erb +6 -0
  151. data/app/views/spree/search/show.turbo_stream.erb +1 -0
  152. data/app/views/spree/search/suggestions.turbo_stream.erb +8 -0
  153. data/app/views/spree/seo/robots.text.erb +31 -0
  154. data/app/views/spree/seo/sitemap.xml.erb +25 -0
  155. data/app/views/spree/shared/_fonts.html.erb +14 -0
  156. data/app/views/spree/shared/_head.html.erb +36 -0
  157. data/app/views/spree/shared/_load_more_products.turbo_stream.erb +7 -0
  158. data/app/views/spree/shared/_product_listing_page.html.erb +11 -0
  159. data/app/views/spree/shared/_products.html.erb +1 -0
  160. data/app/views/spree/taxonomies/show.html.erb +1 -0
  161. data/app/views/spree/taxons/show.html.erb +1 -0
  162. data/app/views/spree/taxons/show.turbo_stream.erb +1 -0
  163. data/app/views/spree/waitlists/create.turbo_stream.erb +9 -0
  164. data/app/views/themes/default/kaminari/storefront/_first_page.html.erb +8 -0
  165. data/app/views/themes/default/kaminari/storefront/_gap.html.erb +8 -0
  166. data/app/views/themes/default/kaminari/storefront/_last_page.html.erb +8 -0
  167. data/app/views/themes/default/kaminari/storefront/_next_page.html.erb +13 -0
  168. data/app/views/themes/default/kaminari/storefront/_page.html.erb +10 -0
  169. data/app/views/themes/default/kaminari/storefront/_paginator.html.erb +27 -0
  170. data/app/views/themes/default/kaminari/storefront/_prev_page.html.erb +13 -0
  171. data/app/views/themes/default/spree/account/_account_nav.html.erb +46 -0
  172. data/app/views/themes/default/spree/account/_order.html.erb +39 -0
  173. data/app/views/themes/default/spree/account/_orders.html.erb +16 -0
  174. data/app/views/themes/default/spree/account/addresses/_address.html.erb +42 -0
  175. data/app/views/themes/default/spree/account/addresses/index.html.erb +28 -0
  176. data/app/views/themes/default/spree/account/newsletter/_newsletter_settings.html.erb +13 -0
  177. data/app/views/themes/default/spree/account/newsletter/edit.html.erb +16 -0
  178. data/app/views/themes/default/spree/account/newsletter/update.html.erb +1 -0
  179. data/app/views/themes/default/spree/account/orders/index.html.erb +24 -0
  180. data/app/views/themes/default/spree/account/orders/show.html.erb +22 -0
  181. data/app/views/themes/default/spree/account/profile/edit.html.erb +36 -0
  182. data/app/views/themes/default/spree/account/store_credits/_store_credit_event.html.erb +29 -0
  183. data/app/views/themes/default/spree/account/store_credits/index.html.erb +31 -0
  184. data/app/views/themes/default/spree/checkout/_footer.html.erb +10 -0
  185. data/app/views/themes/default/spree/checkout/complete.html.erb +84 -0
  186. data/app/views/themes/default/spree/contacts/new.html.erb +23 -0
  187. data/app/views/themes/default/spree/orders/_cart.html.erb +14 -0
  188. data/app/views/themes/default/spree/orders/_empty.html.erb +11 -0
  189. data/app/views/themes/default/spree/orders/_line_item.html.erb +47 -0
  190. data/app/views/themes/default/spree/orders/_line_item_quantity.html.erb +11 -0
  191. data/app/views/themes/default/spree/orders/_summary.html.erb +41 -0
  192. data/app/views/themes/default/spree/orders/edit.html.erb +18 -0
  193. data/app/views/themes/default/spree/orders/show.html.erb +6 -0
  194. data/app/views/themes/default/spree/page_sections/_announcement_bar.html.erb +10 -0
  195. data/app/views/themes/default/spree/page_sections/_custom_code.html.erb +5 -0
  196. data/app/views/themes/default/spree/page_sections/_featured_product.html.erb +136 -0
  197. data/app/views/themes/default/spree/page_sections/_featured_taxon.html.erb +116 -0
  198. data/app/views/themes/default/spree/page_sections/_featured_taxons.html.erb +71 -0
  199. data/app/views/themes/default/spree/page_sections/_footer.html.erb +62 -0
  200. data/app/views/themes/default/spree/page_sections/_header.html.erb +166 -0
  201. data/app/views/themes/default/spree/page_sections/_image_banner.html.erb +57 -0
  202. data/app/views/themes/default/spree/page_sections/_image_with_text.html.erb +66 -0
  203. data/app/views/themes/default/spree/page_sections/_main_password_footer.html.erb +64 -0
  204. data/app/views/themes/default/spree/page_sections/_main_password_header.html.erb +54 -0
  205. data/app/views/themes/default/spree/page_sections/_newsletter.html.erb +47 -0
  206. data/app/views/themes/default/spree/page_sections/_page_title.html.erb +7 -0
  207. data/app/views/themes/default/spree/page_sections/_post_details.html.erb +19 -0
  208. data/app/views/themes/default/spree/page_sections/_post_grid.html.erb +11 -0
  209. data/app/views/themes/default/spree/page_sections/_product_details.html.erb +102 -0
  210. data/app/views/themes/default/spree/page_sections/_product_grid.html.erb +52 -0
  211. data/app/views/themes/default/spree/page_sections/_related_products.html.erb +15 -0
  212. data/app/views/themes/default/spree/page_sections/_rich_text.html.erb +18 -0
  213. data/app/views/themes/default/spree/page_sections/_taxon_banner.html.erb +37 -0
  214. data/app/views/themes/default/spree/page_sections/_taxon_grid.html.erb +103 -0
  215. data/app/views/themes/default/spree/page_sections/_video.html.erb +27 -0
  216. data/app/views/themes/default/spree/page_sections/nav/_desktop.html.erb +49 -0
  217. data/app/views/themes/default/spree/page_sections/nav/_mobile.html.erb +136 -0
  218. data/app/views/themes/default/spree/policies/show.html.erb +11 -0
  219. data/app/views/themes/default/spree/posts/_json_ld.html.erb +20 -0
  220. data/app/views/themes/default/spree/posts/_pagination.html.erb +1 -0
  221. data/app/views/themes/default/spree/posts/_post.html.erb +13 -0
  222. data/app/views/themes/default/spree/products/_add_to_cart_button.html.erb +58 -0
  223. data/app/views/themes/default/spree/products/_add_to_waitlist.html.erb +36 -0
  224. data/app/views/themes/default/spree/products/_add_to_wishlist.html.erb +33 -0
  225. data/app/views/themes/default/spree/products/_breadcrumbs.html.erb +23 -0
  226. data/app/views/themes/default/spree/products/_color_picker.html.erb +19 -0
  227. data/app/views/themes/default/spree/products/_color_swatches.html.erb +61 -0
  228. data/app/views/themes/default/spree/products/_details.html.erb +55 -0
  229. data/app/views/themes/default/spree/products/_featured_image.html.erb +37 -0
  230. data/app/views/themes/default/spree/products/_filters.html.erb +45 -0
  231. data/app/views/themes/default/spree/products/_json_ld.html.erb +38 -0
  232. data/app/views/themes/default/spree/products/_json_ld_list.html.erb +9 -0
  233. data/app/views/themes/default/spree/products/_json_ld_variant.html.erb +10 -0
  234. data/app/views/themes/default/spree/products/_label.html.erb +26 -0
  235. data/app/views/themes/default/spree/products/_media_gallery.html.erb +94 -0
  236. data/app/views/themes/default/spree/products/_price.html.erb +59 -0
  237. data/app/views/themes/default/spree/products/_product.html.erb +62 -0
  238. data/app/views/themes/default/spree/products/_quantity_selector.html.erb +32 -0
  239. data/app/views/themes/default/spree/products/_returns_policy_modal.html.erb +22 -0
  240. data/app/views/themes/default/spree/products/_show_more_button.html.erb +8 -0
  241. data/app/views/themes/default/spree/products/_sort.html.erb +45 -0
  242. data/app/views/themes/default/spree/products/_swiper.html.erb +85 -0
  243. data/app/views/themes/default/spree/products/_tags.html.erb +0 -0
  244. data/app/views/themes/default/spree/products/_variant_options.html.erb +71 -0
  245. data/app/views/themes/default/spree/products/_variant_picker.html.erb +50 -0
  246. data/app/views/themes/default/spree/products/filters/_availability.html.erb +32 -0
  247. data/app/views/themes/default/spree/products/filters/_colors.html.erb +23 -0
  248. data/app/views/themes/default/spree/products/filters/_generic.html.erb +75 -0
  249. data/app/views/themes/default/spree/products/filters/_price.html.erb +35 -0
  250. data/app/views/themes/default/spree/products/filters/_taxons.erb +78 -0
  251. data/app/views/themes/default/spree/search/_suggestions.html.erb +92 -0
  252. data/app/views/themes/default/spree/settings/show.html.erb +32 -0
  253. data/app/views/themes/default/spree/shared/_account_pane.html.erb +28 -0
  254. data/app/views/themes/default/spree/shared/_address.html.erb +35 -0
  255. data/app/views/themes/default/spree/shared/_cart_icon.html.erb +13 -0
  256. data/app/views/themes/default/spree/shared/_cart_pane.html.erb +38 -0
  257. data/app/views/themes/default/spree/shared/_css_variables.html.erb +54 -0
  258. data/app/views/themes/default/spree/shared/_custom_head.html.erb +0 -0
  259. data/app/views/themes/default/spree/shared/_error_messages.html.erb +9 -0
  260. data/app/views/themes/default/spree/shared/_error_messages_without_base_attribute.html.erb +15 -0
  261. data/app/views/themes/default/spree/shared/_flash.html.erb +16 -0
  262. data/app/views/themes/default/spree/shared/_flashes.html.erb +10 -0
  263. data/app/views/themes/default/spree/shared/_json_ld.html.erb +32 -0
  264. data/app/views/themes/default/spree/shared/_line_item_options.html.erb +18 -0
  265. data/app/views/themes/default/spree/shared/_logo.html.erb +42 -0
  266. data/app/views/themes/default/spree/shared/_meta_tags.html.erb +45 -0
  267. data/app/views/themes/default/spree/shared/_order_details.html.erb +106 -0
  268. data/app/views/themes/default/spree/shared/_order_line_item.html.erb +65 -0
  269. data/app/views/themes/default/spree/shared/_order_shipment.html.erb +71 -0
  270. data/app/views/themes/default/spree/shared/_search.html.erb +32 -0
  271. data/app/views/themes/default/spree/shared/_title.html.erb +3 -0
  272. data/app/views/themes/default/spree/shared/_wishlist_icon.html.erb +13 -0
  273. data/app/views/themes/default/spree/shared/icons/_account.html.erb +17 -0
  274. data/app/views/themes/default/spree/shared/icons/_arrow-left.html.erb +8 -0
  275. data/app/views/themes/default/spree/shared/icons/_arrow-right.html.erb +3 -0
  276. data/app/views/themes/default/spree/shared/icons/_bell.html.erb +9 -0
  277. data/app/views/themes/default/spree/shared/icons/_cart.html.erb +10 -0
  278. data/app/views/themes/default/spree/shared/icons/_cart_48.html.erb +6 -0
  279. data/app/views/themes/default/spree/shared/icons/_check.html.erb +4 -0
  280. data/app/views/themes/default/spree/shared/icons/_chevron.html.erb +15 -0
  281. data/app/views/themes/default/spree/shared/icons/_chevron_down.html.erb +5 -0
  282. data/app/views/themes/default/spree/shared/icons/_chevron_right.html.erb +15 -0
  283. data/app/views/themes/default/spree/shared/icons/_chevron_up.html.erb +3 -0
  284. data/app/views/themes/default/spree/shared/icons/_close.html.erb +9 -0
  285. data/app/views/themes/default/spree/shared/icons/_cross.html.erb +16 -0
  286. data/app/views/themes/default/spree/shared/icons/_delete.html.erb +3 -0
  287. data/app/views/themes/default/spree/shared/icons/_delivery.html.erb +5 -0
  288. data/app/views/themes/default/spree/shared/icons/_disabled.html.erb +13 -0
  289. data/app/views/themes/default/spree/shared/icons/_edit.html.erb +3 -0
  290. data/app/views/themes/default/spree/shared/icons/_facebook.html.erb +16 -0
  291. data/app/views/themes/default/spree/shared/icons/_filter.html.erb +8 -0
  292. data/app/views/themes/default/spree/shared/icons/_heart.html.erb +12 -0
  293. data/app/views/themes/default/spree/shared/icons/_info.html.erb +7 -0
  294. data/app/views/themes/default/spree/shared/icons/_instagram.html.erb +18 -0
  295. data/app/views/themes/default/spree/shared/icons/_lock.html.erb +13 -0
  296. data/app/views/themes/default/spree/shared/icons/_menu.html.erb +10 -0
  297. data/app/views/themes/default/spree/shared/icons/_minus.html.erb +5 -0
  298. data/app/views/themes/default/spree/shared/icons/_pinch.html.erb +6 -0
  299. data/app/views/themes/default/spree/shared/icons/_pinterest.html.erb +8 -0
  300. data/app/views/themes/default/spree/shared/icons/_plus.html.erb +17 -0
  301. data/app/views/themes/default/spree/shared/icons/_return.html.erb +11 -0
  302. data/app/views/themes/default/spree/shared/icons/_search.html.erb +17 -0
  303. data/app/views/themes/default/spree/shared/icons/_spinner.html.erb +1 -0
  304. data/app/views/themes/default/spree/shared/icons/_spotify.html.erb +8 -0
  305. data/app/views/themes/default/spree/shared/icons/_tiktok.html.erb +9 -0
  306. data/app/views/themes/default/spree/shared/icons/_twitter.html.erb +16 -0
  307. data/app/views/themes/default/spree/shared/icons/_youtube.html.erb +9 -0
  308. data/app/views/themes/default/spree/shared/icons/_zoom.html.erb +10 -0
  309. data/app/views/themes/default/spree/wishlists/_no_wished_items.html.erb +10 -0
  310. data/app/views/themes/default/spree/wishlists/_wished_item.html.erb +38 -0
  311. data/app/views/themes/default/spree/wishlists/show.html.erb +17 -0
  312. data/config/i18n-tasks.yml +176 -0
  313. data/config/importmap.rb +22 -0
  314. data/config/initializers/assets.rb +1 -0
  315. data/config/initializers/heroicon.rb +10 -0
  316. data/config/locales/en.yml +76 -0
  317. data/config/routes.rb +88 -0
  318. data/lib/generators/spree/storefront/install/install_generator.rb +45 -0
  319. data/lib/generators/spree/storefront/install/templates/application.tailwind.css +1760 -0
  320. data/lib/generators/spree/storefront/install/templates/dev +16 -0
  321. data/lib/generators/spree/storefront/install/templates/tailwind.config.js +128 -0
  322. data/lib/generators/spree/storefront/theme/templates/model.rb.tt +12 -0
  323. data/lib/generators/spree/storefront/theme/theme_generator.rb +41 -0
  324. data/lib/spree/storefront/configuration.rb +11 -0
  325. data/lib/spree/storefront/engine.rb +51 -0
  326. data/lib/spree/storefront/testing_support/capybara_utils.rb +13 -0
  327. data/lib/spree/storefront.rb +16 -0
  328. data/lib/spree_storefront.rb +1 -0
  329. data/vendor/colornames.json +1 -0
  330. data/vendor/javascript/@kanety--stimulus-accordion.js +4 -0
  331. data/vendor/javascript/@stimulus-components--carousel.js +4 -0
  332. data/vendor/javascript/card-validator.js +4 -0
  333. data/vendor/javascript/credit-card-type.js +4 -0
  334. data/vendor/javascript/headroom.js.js +19 -0
  335. data/vendor/javascript/nouislider.js +4 -0
  336. data/vendor/javascript/photoswipe--dist--photoswipe-lightbox.esm.js.js +667 -0
  337. data/vendor/javascript/photoswipe.js +1675 -0
  338. data/vendor/javascript/stimulus-read-more.js +4 -0
  339. data/vendor/javascript/stimulus-scroll-to.js +4 -0
  340. data/vendor/javascript/swiper--bundle.js +4 -0
  341. metadata +567 -0
@@ -0,0 +1,180 @@
1
+ module Spree
2
+ module FiltersHelper
3
+ def products_for_filters_default_scope
4
+ scope = storefront_products_scope
5
+ scope = scope.in_taxon(@taxon) if @taxon.present?
6
+ scope = scope.search_by_name(params[:q].strip).unscope(:order) if params[:q].present?
7
+ scope
8
+ end
9
+
10
+ def products_for_filters_scope(except_filters: [])
11
+ products_finder = Spree::Dependencies.products_finder.constantize
12
+
13
+ finder_params = default_products_finder_params
14
+ finder_params[:filter] = finder_params[:filter].except(*except_filters) if except_filters.any?
15
+
16
+ products = products_finder.new(scope: storefront_products_scope, params: finder_params).execute
17
+ current_store.products.where(id: products.unscope(:order).ids)
18
+ end
19
+
20
+ def storefront_products_for_filters
21
+ @storefront_products_for_filters ||= products_for_filters_scope
22
+ end
23
+
24
+ def default_storefront_products_for_filters
25
+ @default_storefront_products_for_filters ||= products_for_filters_default_scope.unscope(:order)
26
+ end
27
+
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
34
+ end
35
+
36
+ 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
42
+ end
43
+
44
+ def products_count_for_filter(name, value_id)
45
+ aggregations = single_option_filter_selected?(name) ? product_single_filter_aggregations : product_filters_aggregations
46
+ aggregations.fetch(value_id, 0)
47
+ end
48
+
49
+ def filter_price_range
50
+ @filter_price_range ||= begin
51
+ product_ids = products_for_filters_scope(except_filters: [:price]).ids
52
+
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
+ end
58
+ end
59
+
60
+ def filter_stock_count
61
+ @filter_stock_count ||= begin
62
+ products_scope = products_for_filters_scope(except_filters: [:purchasable, :out_of_stock])
63
+
64
+ {
65
+ in_stock: products_scope.in_stock.distinct.count,
66
+ out_of_stock: products_scope.out_of_stock.distinct.count
67
+ }
68
+ end
69
+ end
70
+
71
+ def default_storefront_filter_values_scope
72
+ @default_storefront_filter_values_scope ||= Spree::OptionValue.for_products(products_for_filters_default_scope).distinct
73
+ end
74
+
75
+ def storefront_filter_values_scope(filter_selected)
76
+ if filter_selected
77
+ default_storefront_filter_values_scope
78
+ else
79
+ Spree::OptionValue.for_products(storefront_products_for_filters).distinct
80
+ end
81
+ end
82
+
83
+ def filter_values_for_filter(filter)
84
+ selected = single_option_filter_selected?(filter.name)
85
+ if filter.option_values.loaded?
86
+ storefront_filter_values_ids = storefront_filter_values_scope(selected).pluck(:id)
87
+
88
+ filter.option_values.find_all { |option_value| storefront_filter_values_ids.include?(option_value.id) }
89
+ else
90
+ filter.option_values.where(id: storefront_filter_values_scope(selected))
91
+ end
92
+ end
93
+
94
+ def storefront_products_for_taxon_filters
95
+ @storefront_products_for_taxon_filters ||= products_for_filters_scope(except_filters: [:taxons])
96
+ end
97
+
98
+ def filter_taxon_ids
99
+ @filter_taxon_ids ||= Rails.cache.fetch([spree_base_cache_key, storefront_products_for_taxon_filters, @taxon], expires_in: 1.day) do
100
+ scope = storefront_products_for_taxon_filters.joins(:classifications)
101
+ scope = scope.in_taxon(@taxon).unscope(:order) if @taxon.present?
102
+ scope.distinct.pluck(:taxon_id)
103
+ end
104
+ end
105
+
106
+ def filter_taxons_for_taxonomy(taxonomy)
107
+ taxon_depth = @taxon.present? ? @taxon.depth + 1 : 1
108
+
109
+ taxonomy.taxons.where(depth: taxon_depth).order(:lft).find_all do |taxon|
110
+ (taxon.cached_self_and_descendants_ids & filter_taxon_ids).any?
111
+ end
112
+ end
113
+
114
+ def product_taxon_aggregations
115
+ @product_taxon_aggregations ||= Rails.cache.fetch([spree_base_cache_key, storefront_products_for_taxon_filters, current_store.taxons], expires_in: 1.day) do
116
+ current_store.taxons.
117
+ joins(:classifications).
118
+ where("#{Spree::Classification.table_name}.product_id" => storefront_products_for_taxon_filters.ids).
119
+ group(
120
+ "#{Spree::Taxon.table_name}.id",
121
+ "#{Spree::Classification.table_name}.product_id"
122
+ ).
123
+ unscope(:order).
124
+ count("#{Spree::Classification.table_name}.product_id")
125
+ end
126
+ end
127
+
128
+ def products_count_for_taxon(taxon)
129
+ taxon_self_and_descendants_ids = taxon.cached_self_and_descendants_ids
130
+ aggregations = product_taxon_aggregations.select { |key, _value| key[0].in?(taxon_self_and_descendants_ids) }
131
+
132
+ # We want to count unique products across the taxon tree
133
+ aggregations.uniq { |key, _value| key[1] }.sum(&:last)
134
+ end
135
+
136
+ def filter_form_fields
137
+ return '' if products_filters_params.blank?
138
+
139
+ map_filters_params_to_form_fields(products_filters_params)
140
+ end
141
+
142
+ def map_filters_params_to_form_fields(filters_params, prefix_key = nil)
143
+ filters_params.to_h.map do |key, value|
144
+ key = CGI.escapeHTML(key)
145
+ key = [prefix_key, key].join('][') if prefix_key.present?
146
+
147
+ if value.is_a?(Array)
148
+ value.map do |v|
149
+ v = CGI.escapeHTML(v)
150
+ "<input type='hidden' name='filter[#{key}][]' value='#{v}' />"
151
+ end.join
152
+ elsif value.is_a?(Hash)
153
+ map_filters_params_to_form_fields(value, key)
154
+ else
155
+ value = CGI.escapeHTML(value)
156
+ "<input type='hidden' name='filter[#{key}]' value='#{value}' />"
157
+ end
158
+ end.join.html_safe
159
+ end
160
+
161
+ def single_option_filter_selected?(filter_name)
162
+ options_filter = permitted_products_params.dig(:filter, :options) || {}
163
+
164
+ !price_filtered? && !taxons_filtered? &&
165
+ options_filter.keys.length == 1 && options_filter.key?(filter_name)
166
+ end
167
+
168
+ def price_filtered?
169
+ min_price = permitted_products_params.dig(:filter, :min_price).presence
170
+ max_price = permitted_products_params.dig(:filter, :max_price).presence
171
+
172
+ (min_price.present? && min_price.to_d >= filter_price_range[:min]) ||
173
+ (max_price.present? && max_price.to_d <= filter_price_range[:max])
174
+ end
175
+
176
+ def taxons_filtered?
177
+ permitted_products_params.dig(:filter, :taxon_ids)&.any?
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,58 @@
1
+ module Spree
2
+ module FontsHelper
3
+ def normal_font_family_styles
4
+ return '' if current_theme_or_preview.preferred_font_family.blank?
5
+
6
+ font_family = current_theme_or_preview.preferred_font_family
7
+ text_font_size = (current_theme_or_preview.preferred_font_size_scale || 100) / 100.0
8
+
9
+ <<~CSS
10
+ :root {
11
+ --font-body: #{font_family};
12
+ }
13
+
14
+ body {
15
+ font-family: '#{font_family}', sans-serif;
16
+ }
17
+
18
+ body {
19
+ font-size: #{text_font_size}rem;
20
+ }
21
+ CSS
22
+ end
23
+
24
+ def header_font_family_styles
25
+ return '' if current_theme_or_preview.preferred_header_font_family.blank?
26
+
27
+ font_family = current_theme_or_preview.preferred_header_font_family
28
+
29
+ <<~CSS
30
+ h1, h2, h3, h4, h5, h6 {
31
+ font-family: '#{font_family}', sans-serif;
32
+ }
33
+ CSS
34
+ end
35
+
36
+ def google_fonts_link_tag
37
+ fonts = [current_theme_or_preview.preferred_font_family, current_theme_or_preview.preferred_header_font_family].compact_blank.uniq
38
+
39
+ return if fonts.blank?
40
+
41
+ imports = fonts.map do |font|
42
+ "family=#{font.split(' ').join('+')}:wght@200..700"
43
+ end.compact.join('&')
44
+
45
+ return if imports.blank?
46
+ return if Rails.env.test?
47
+
48
+ stylesheet_link_tag "https://fonts.googleapis.com/css2?#{imports}&display=swap"
49
+ end
50
+
51
+ def font_styles
52
+ <<~CSS.html_safe
53
+ #{normal_font_family_styles}
54
+ #{header_font_family_styles}
55
+ CSS
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ module OrdersHelper
3
+ def order_just_completed?
4
+ @order.present? && @order.completed? && session[:checkout_completed].present?
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,67 @@
1
+ module Spree
2
+ module PageHelper
3
+ def render_page(page = nil, variables = {})
4
+ page ||= current_page
5
+
6
+ sections = current_page_preview.present? ? current_page_preview.sections : page.sections
7
+ sections_html = sections.includes(:links, { asset_attachment: :blob }, { blocks: [:rich_text_text, :links] }).map do |section|
8
+ render_section(section, variables)
9
+ end.join.html_safe
10
+
11
+ "<main class='page-contents'>#{sections_html}</main>".html_safe
12
+ end
13
+
14
+ def render_section(section, variables = {}, lazy_allowed: true)
15
+ return '' if section.blank?
16
+
17
+ variables[:section] = section
18
+ variables[:loaded] = true
19
+
20
+ css_id = "section-#{section.id}"
21
+ css_class = "section-#{section.class.name.demodulize.underscore.dasherize}"
22
+
23
+ if page_builder_enabled?
24
+ turbo_frame_tag(css_id, class: css_class) do
25
+ content_tag(:div,
26
+ data: {
27
+ editor_id: css_id,
28
+ editor_name: section.name,
29
+ editor_link: spree.edit_admin_page_section_path(section)
30
+ }
31
+ ) do
32
+ render('/' + section.to_partial_path, **variables)
33
+ end
34
+ end
35
+ elsif section.lazy? && lazy_allowed
36
+ variables[:loaded] = false
37
+ variables[:url_options] = { locale: I18n.locale }
38
+
39
+ path = section.lazy_path(variables)
40
+
41
+ turbo_frame_tag(css_id, src: path, loading: 'eager', class: css_class) do
42
+ render('/' + section.to_partial_path, **variables)
43
+ end
44
+ else
45
+ content_tag(:div, id: css_id, class: css_class) do
46
+ render('/' + section.to_partial_path, **variables)
47
+ end
48
+ end
49
+ rescue ActionView::MissingTemplate, ActionView::Template::Error => e
50
+ raise e unless Rails.env.production?
51
+
52
+ Rails.error.report(e, context: { section_id: section.id }, source: 'spree.storefront')
53
+
54
+ ''
55
+ end
56
+
57
+ def page_builder_link_to(link, options = {}, &block)
58
+ if link.present?
59
+ link_to(spree_storefront_resource_url(link.linkable || link), options.except(:label)) do
60
+ block.present? ? block.call : options[:label]
61
+ end
62
+ else
63
+ block&.call&.html_safe
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ module PostsHelper
3
+ def posts_json_ld_breadcrumbs(post)
4
+ json_ld = {
5
+ '@context' => 'https://schema.org',
6
+ '@type' => 'BreadcrumbList',
7
+ 'itemListElement' => [
8
+ {
9
+ '@type' => 'ListItem',
10
+ 'position' => 1,
11
+ 'name' => Spree.t(:homepage),
12
+ 'item' => spree.root_url(host: current_store.url_or_custom_domain)
13
+ },
14
+ {
15
+ '@type' => 'ListItem',
16
+ 'position' => 2,
17
+ 'name' => Spree.t(:blog),
18
+ 'item' => spree.posts_url(host: current_store.url_or_custom_domain)
19
+ }
20
+ ]
21
+ }
22
+
23
+ post_breadcrumb = { '@type' => 'ListItem', 'name' => post.title }
24
+ post_category = post.post_category
25
+
26
+ if post_category.present?
27
+ json_ld['itemListElement'] << {
28
+ '@type' => 'ListItem',
29
+ 'position' => 3,
30
+ 'name' => post_category.title,
31
+ 'item' => spree.category_posts_url(category_id: post.post_category.slug, host: current_store.url_or_custom_domain)
32
+ }
33
+
34
+ json_ld['itemListElement'] << post_breadcrumb.merge('position' => 4)
35
+ else
36
+ json_ld['itemListElement'] << post_breadcrumb.merge('position' => 3)
37
+ end
38
+
39
+ json_ld
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,205 @@
1
+ module Spree
2
+ module ProductsHelper
3
+ include BaseHelper
4
+
5
+ def product_cache_key(product)
6
+ [
7
+ current_theme,
8
+ current_store,
9
+ try_spree_current_user,
10
+ current_currency,
11
+ I18n.locale,
12
+ product.cache_key_with_version,
13
+ product.available?,
14
+ product.discontinued?,
15
+ ]
16
+ end
17
+
18
+ def should_display_compare_at_price?(product_or_variant)
19
+ price = product_or_variant.price_in(current_currency)
20
+ price.compare_at_amount.present? && (price.compare_at_amount > price.amount)
21
+ end
22
+
23
+ def weeks_online(product)
24
+ (Time.current - product.activated_at.in_time_zone(current_store.preferred_timezone)).seconds.in_weeks.to_i.abs
25
+ end
26
+
27
+ def brand_name(product)
28
+ product.brand&.name || product.vendor&.display_name
29
+ end
30
+
31
+ def product_not_selected_options(product, selected_variant, options_param_name: :options)
32
+ product.option_types.map do |option_type|
33
+ if product_selected_option_for_option(
34
+ option_type,
35
+ product: product,
36
+ selected_variant: selected_variant,
37
+ options_param_name: options_param_name
38
+ ).present?
39
+ next
40
+ else
41
+ option_type
42
+ end
43
+ end.compact_blank
44
+ end
45
+
46
+ def product_selected_option_for_option(option_type, product:, selected_variant: nil, options_param_name: :options)
47
+ @memoized_values ||= {}
48
+ memoized_key = "#{product.id}-#{selected_variant&.id}-#{option_type.id}-#{options_param_name}"
49
+
50
+ product_selected_options_hash = if params[options_param_name].present?
51
+ params[options_param_name].split(',').to_h do |option|
52
+ key, *value = option.split(':')
53
+ [key, value.join(':')]
54
+ end
55
+ else
56
+ {}
57
+ end
58
+
59
+ @memoized_values[memoized_key] ||=
60
+ if selected_variant.present?
61
+ selected_variant.option_values.find { |ov| ov.option_type_id == option_type.id }
62
+ elsif product_selected_options_hash.present? && product_selected_options_hash[option_type.id.to_s].present? # user selected variant which is not available
63
+ option_type.option_values.find { |v| v.name == product_selected_options_hash[option_type.id.to_s] }
64
+ elsif option_type.color? && (available_variant = product.first_available_variant(current_currency))
65
+ available_variant.option_values.find { |ov| ov.option_type_id == option_type.id }
66
+ elsif option_type.color? && product.first_available_variant(current_currency).nil?
67
+ product.variants.first&.option_values&.find { |ov| ov.option_type_id == option_type.id }
68
+ end
69
+ end
70
+
71
+ def product_option_values_for_option(option_type, product:)
72
+ product.option_values.includes(:option_type).
73
+ find_all { |option_value| option_value.option_type_id == option_type.id }.
74
+ uniq { |ov| [ov.name, ov.option_type&.name] }
75
+ end
76
+
77
+ def product_media_gallery_images(product, selected_variant:, variant_from_options:, options_param_name: :options)
78
+ images = selected_variant&.images&.to_a || []
79
+ images ||= variant_from_options&.images&.to_a if images.empty?
80
+
81
+ if images.compact.empty?
82
+ first_option_type = product.option_types.first
83
+
84
+ if first_option_type.present?
85
+ variant_for_first_option = product_variant_for_selected_option(
86
+ first_option_type, product: product, selected_variant: selected_variant, options_param_name: options_param_name
87
+ )
88
+
89
+ images << variant_for_first_option.images.to_a if variant_for_first_option.present?
90
+ end
91
+ end
92
+
93
+ images << product.master&.images&.to_a
94
+ images << product.default_image if images.flatten.compact.empty?
95
+
96
+ images.flatten.compact.uniq(&:id)
97
+ end
98
+
99
+ def variant_featured_image(variant)
100
+ return nil unless variant
101
+
102
+ variant.images.first
103
+ end
104
+
105
+ def image_alt(image)
106
+ @memoized_image_alts ||= {}
107
+ key = image.id
108
+
109
+ @memoized_image_alts[key] ||=
110
+ image.alt.presence ||
111
+ (image.viewable.descriptive_name if image.viewable_type == 'Spree::Variant') ||
112
+ @product.name
113
+ end
114
+
115
+ def product_variant_for_selected_option(option_type, product:, selected_variant:, options_param_name: :options)
116
+ selected_value_for_option = product_selected_option_for_option(
117
+ option_type,
118
+ product: product,
119
+ selected_variant: selected_variant,
120
+ options_param_name: options_param_name
121
+ )
122
+ return unless selected_value_for_option.present?
123
+
124
+ product.variants.with_option_value(option_type.name, selected_value_for_option.name).first
125
+ end
126
+
127
+ def products_selected_filters_count
128
+ params[:filters].present? ? params[:filters].split(',').count : 0
129
+ end
130
+
131
+ def products_sort
132
+ @products_sort ||= permitted_products_params[:sort_by]&.strip_html_tags.presence || default_products_sort
133
+ end
134
+
135
+ def taxons_sort_options
136
+ @taxons_sort_options ||= [
137
+ { name: Spree.t('products_sort_options.relevance'), value: 'manual' },
138
+ { name: Spree.t('products_sort_options.best_selling'), value: 'best-selling' },
139
+ { name: Spree.t('products_sort_options.name_a_z'), value: 'name-a-z' },
140
+ { name: Spree.t('products_sort_options.name_z_a'), value: 'name-z-a' },
141
+ { name: Spree.t('products_sort_options.price_low_to_high'), value: 'price-low-to-high' },
142
+ { name: Spree.t('products_sort_options.price_high_to_low'), value: 'price-high-to-low' },
143
+ { name: Spree.t('products_sort_options.newest_first'), value: 'newest-first' },
144
+ { name: Spree.t('products_sort_options.oldest_first'), value: 'oldest-first' }
145
+ ]
146
+ end
147
+
148
+ def product_breadcrumb_taxons(product)
149
+ return Spree::Taxon.none if product.main_taxon.blank?
150
+
151
+ # using find_all as we already iterate over the taxons in #product_json_ld_breadcrumbs
152
+ product.main_taxon.self_and_ancestors.find_all { |taxon| taxon.depth != 0 }
153
+ end
154
+
155
+ def product_list_json_ld_elements(product_slugs)
156
+ product_slugs.each_with_index.map do |product_slug, index|
157
+ {
158
+ '@type' => 'ListItem',
159
+ 'position' => index + 1,
160
+ 'url' => spree.product_url(product_slug)
161
+ }
162
+ end
163
+ end
164
+
165
+ def product_json_ld_breadcrumbs(product)
166
+ json_ld = {
167
+ '@context' => 'https://schema.org',
168
+ '@type' => 'BreadcrumbList',
169
+ 'itemListElement' => [
170
+ {
171
+ '@type': 'ListItem',
172
+ 'position' => 1,
173
+ 'name' => 'Homepage',
174
+ 'item' => spree.root_url(host: current_store.url_or_custom_domain)
175
+ }
176
+ ]
177
+ }
178
+
179
+ if product.main_taxon.present?
180
+ product.main_taxon.self_and_ancestors.each_with_index do |taxon, index|
181
+ json_ld['itemListElement'] << {
182
+ '@type' => 'ListItem',
183
+ 'position' => index + 2,
184
+ 'name' => taxon.name,
185
+ 'item' => spree.nested_taxons_url(taxon, host: current_store.url_or_custom_domain)
186
+ }
187
+ end
188
+ end
189
+
190
+ json_ld['itemListElement'] << {
191
+ '@type' => 'ListItem',
192
+ 'position' => json_ld['itemListElement'].length + 1,
193
+ 'name' => product.name
194
+ }
195
+
196
+ json_ld
197
+ end
198
+
199
+ def option_type_colors_preview_styles(option_type)
200
+ return unless option_type.color?
201
+
202
+ Spree::ColorsPreviewStylesPresenter.new(option_type.option_values.map { |ov| { name: ov.name, filter_name: ov.name } }).to_s
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,86 @@
1
+ module Spree
2
+ module StorefrontHelper
3
+ include BaseHelper
4
+ include Heroicon::Engine.helpers
5
+
6
+ def render_storefront_partials(section)
7
+ Rails.application.config.spree_storefront.send(section).map do |partial|
8
+ render partial: partial
9
+ end.join.html_safe
10
+ end
11
+
12
+ def page_description
13
+ return @page_description if @page_description.present?
14
+
15
+ @page_description = object.meta_description if object.respond_to?(:meta_description)
16
+
17
+ if @page_description.blank? && [Spree::Product, Spree::Taxon, Spree::Post].include?(object.class)
18
+ @page_description = truncate(strip_tags(object.description.to_s).strip, length: 160, separator: ' ', escape: false)
19
+ end
20
+
21
+ @page_description = current_store.meta_description if @page_description.blank?
22
+
23
+ @page_description
24
+ end
25
+
26
+ def page_image
27
+ return @page_image if @page_image.present?
28
+
29
+ if object.is_a? Spree::Product
30
+ @page_image = object.default_image&.attachment
31
+ elsif object.respond_to?(:image)
32
+ @page_image = object.image
33
+ end
34
+
35
+ @page_image ||= current_store.social_image
36
+
37
+ @page_image
38
+ end
39
+
40
+ def tailwind_classes_for(flash_type)
41
+ {
42
+ notice: 'alert-notice',
43
+ success: 'alert-success',
44
+ error: 'alert-error',
45
+ alert: 'alert-warning',
46
+ }.stringify_keys[flash_type.to_s] || flash_type.to_s
47
+ end
48
+
49
+ def as_aspect_ratio(attachment)
50
+ return unless attachment.present?
51
+ return unless attachment.analyzed?
52
+
53
+ metadata = attachment.metadata
54
+ aspect_ratio = metadata['aspect_ratio'].presence
55
+
56
+ return aspect_ratio if aspect_ratio
57
+
58
+ width = metadata['width']&.to_f
59
+ return unless width
60
+
61
+ height = metadata['height']&.to_f
62
+ return unless height
63
+
64
+ width / height
65
+ end
66
+
67
+ def svg_country_icon(country_code)
68
+ country_code = :us if country_code.to_s.downcase == 'en'
69
+ tag.span(class: "fi fi-#{country_code.downcase}")
70
+ end
71
+
72
+ def show_account_pane?
73
+ !try_spree_current_user &&
74
+ (defined?(spree_login_path) && !paths_equal?(canonical_path, spree_login_path)) &&
75
+ (defined?(spree_signup_path) && !paths_equal?(canonical_path, spree_signup_path)) &&
76
+ (defined?(spree_forgot_password_path) && !paths_equal?(canonical_path, spree_forgot_password_path))
77
+ end
78
+
79
+ def paths_equal?(path1, path2)
80
+ path1 = URI.parse(path1).path.chomp('/')
81
+ path2 = URI.parse(path2).path.chomp('/')
82
+
83
+ path1 == path2
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ module StorefrontLocaleHelper
3
+ def current_locale
4
+ @current_locale ||= if user_locale?
5
+ try_spree_current_user.selected_locale
6
+ elsif params_locale?
7
+ params[:locale]
8
+ elsif header_locale?
9
+ header_locale
10
+ end
11
+ ensure
12
+ @current_locale ||= default_locale
13
+ end
14
+
15
+ def header_locale?
16
+ header_locale.present?
17
+ end
18
+
19
+ # Taken from rack-contrib
20
+ # https://github.com/rack/rack-contrib/blob/main/lib/rack/contrib/locale.rb#L58
21
+ def header_locale
22
+ header = request.env['HTTP_ACCEPT_LANGUAGE']
23
+
24
+ return if header.nil?
25
+
26
+ locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
27
+ locale, quality = language_tag.split(/;q=/i)
28
+ quality = quality ? quality.to_f : 1.0
29
+ [locale, quality]
30
+ end
31
+ locales = locales.
32
+ reject { |(locale, quality)| locale == '*' || quality.zero? }.
33
+ sort_by { |(_, quality)| quality }.
34
+ map(&:first)
35
+
36
+ locale_from_header = locales.reverse.find { |locale| supported_locale?(locale) }
37
+ locale_from_header ||= locales.reverse.find { |locale| supported_locale?(locale.first(2)) }&.first(2)
38
+
39
+ locale_from_header
40
+ end
41
+ end
42
+ end