spree_page_builder 5.3.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 (219) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +43 -0
  3. data/Rakefile +18 -0
  4. data/app/controllers/concerns/spree/admin/page_builder_concern.rb +45 -0
  5. data/app/controllers/concerns/spree/admin/storefront_breadcrumb_concern.rb +12 -0
  6. data/app/controllers/spree/admin/page_blocks_controller.rb +58 -0
  7. data/app/controllers/spree/admin/page_links_controller.rb +60 -0
  8. data/app/controllers/spree/admin/page_sections_controller.rb +82 -0
  9. data/app/controllers/spree/admin/pages_controller.rb +39 -0
  10. data/app/controllers/spree/admin/storefront_controller.rb +41 -0
  11. data/app/controllers/spree/admin/themes_controller.rb +87 -0
  12. data/app/helpers/spree/admin/page_builder_helper.rb +125 -0
  13. data/app/helpers/spree/admin/themes_helper.rb +9 -0
  14. data/app/jobs/spree/page_builder/products/touch_taxons_job_decorator.rb +15 -0
  15. data/app/jobs/spree/themes/duplicate_components_job.rb +59 -0
  16. data/app/jobs/spree/themes/screenshot_job.rb +81 -0
  17. data/app/models/concerns/spree/has_page_links.rb +53 -0
  18. data/app/models/spree/page.rb +188 -0
  19. data/app/models/spree/page_block.rb +73 -0
  20. data/app/models/spree/page_blocks/buttons.rb +29 -0
  21. data/app/models/spree/page_blocks/heading.rb +18 -0
  22. data/app/models/spree/page_blocks/image.rb +20 -0
  23. data/app/models/spree/page_blocks/link.rb +21 -0
  24. data/app/models/spree/page_blocks/mega_nav.rb +33 -0
  25. data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +32 -0
  26. data/app/models/spree/page_blocks/metafields.rb +18 -0
  27. data/app/models/spree/page_blocks/nav.rb +15 -0
  28. data/app/models/spree/page_blocks/newsletter_form.rb +18 -0
  29. data/app/models/spree/page_blocks/products/brand.rb +15 -0
  30. data/app/models/spree/page_blocks/products/buy_buttons.rb +24 -0
  31. data/app/models/spree/page_blocks/products/description.rb +18 -0
  32. data/app/models/spree/page_blocks/products/price.rb +18 -0
  33. data/app/models/spree/page_blocks/products/quantity_selector.rb +20 -0
  34. data/app/models/spree/page_blocks/products/share.rb +8 -0
  35. data/app/models/spree/page_blocks/products/title.rb +19 -0
  36. data/app/models/spree/page_blocks/products/variant_picker.rb +13 -0
  37. data/app/models/spree/page_blocks/subheading.rb +17 -0
  38. data/app/models/spree/page_blocks/text.rb +16 -0
  39. data/app/models/spree/page_builder/policy_decorator.rb +17 -0
  40. data/app/models/spree/page_builder/post_decorator.rb +17 -0
  41. data/app/models/spree/page_builder/product_decorator.rb +17 -0
  42. data/app/models/spree/page_builder/store_decorator.rb +46 -0
  43. data/app/models/spree/page_builder/taxon_decorator.rb +45 -0
  44. data/app/models/spree/page_link.rb +60 -0
  45. data/app/models/spree/page_section.rb +222 -0
  46. data/app/models/spree/page_sections/announcement_bar.rb +28 -0
  47. data/app/models/spree/page_sections/breadcrumbs.rb +12 -0
  48. data/app/models/spree/page_sections/collection_banner.rb +18 -0
  49. data/app/models/spree/page_sections/custom_code.rb +11 -0
  50. data/app/models/spree/page_sections/featured_posts.rb +45 -0
  51. data/app/models/spree/page_sections/featured_product.rb +50 -0
  52. data/app/models/spree/page_sections/featured_taxon.rb +90 -0
  53. data/app/models/spree/page_sections/featured_taxons.rb +45 -0
  54. data/app/models/spree/page_sections/footer.rb +101 -0
  55. data/app/models/spree/page_sections/header.rb +62 -0
  56. data/app/models/spree/page_sections/image_banner.rb +55 -0
  57. data/app/models/spree/page_sections/image_with_text.rb +65 -0
  58. data/app/models/spree/page_sections/main_password_footer.rb +18 -0
  59. data/app/models/spree/page_sections/main_password_header.rb +20 -0
  60. data/app/models/spree/page_sections/newsletter.rb +54 -0
  61. data/app/models/spree/page_sections/page_title.rb +19 -0
  62. data/app/models/spree/page_sections/post_details.rb +19 -0
  63. data/app/models/spree/page_sections/post_grid.rb +19 -0
  64. data/app/models/spree/page_sections/product_details.rb +53 -0
  65. data/app/models/spree/page_sections/product_grid.rb +13 -0
  66. data/app/models/spree/page_sections/related_products.rb +58 -0
  67. data/app/models/spree/page_sections/rich_text.rb +31 -0
  68. data/app/models/spree/page_sections/taxon_banner.rb +18 -0
  69. data/app/models/spree/page_sections/taxon_grid.rb +17 -0
  70. data/app/models/spree/page_sections/video.rb +107 -0
  71. data/app/models/spree/pages/account.rb +19 -0
  72. data/app/models/spree/pages/cart.rb +19 -0
  73. data/app/models/spree/pages/checkout.rb +15 -0
  74. data/app/models/spree/pages/custom.rb +38 -0
  75. data/app/models/spree/pages/homepage.rb +72 -0
  76. data/app/models/spree/pages/login.rb +19 -0
  77. data/app/models/spree/pages/password.rb +59 -0
  78. data/app/models/spree/pages/post.rb +27 -0
  79. data/app/models/spree/pages/post_list.rb +36 -0
  80. data/app/models/spree/pages/product_details.rb +30 -0
  81. data/app/models/spree/pages/search_results.rb +43 -0
  82. data/app/models/spree/pages/shop_all.rb +40 -0
  83. data/app/models/spree/pages/taxon.rb +29 -0
  84. data/app/models/spree/pages/taxon_list.rb +41 -0
  85. data/app/models/spree/pages/wishlist.rb +15 -0
  86. data/app/models/spree/theme.rb +233 -0
  87. data/app/models/spree/themes/default.rb +97 -0
  88. data/app/services/spree/taxons/touch_featured_sections.rb +21 -0
  89. data/app/views/layouts/spree/page_builder.html.erb +46 -0
  90. data/app/views/spree/admin/dashboard/_store_preview.html.erb +38 -0
  91. data/app/views/spree/admin/page_blocks/_form_tab_buttons.html.erb +5 -0
  92. data/app/views/spree/admin/page_blocks/create.turbo_stream.erb +4 -0
  93. data/app/views/spree/admin/page_blocks/destroy.turbo_stream.erb +9 -0
  94. data/app/views/spree/admin/page_blocks/edit.html.erb +43 -0
  95. data/app/views/spree/admin/page_blocks/forms/_brand.html.erb +15 -0
  96. data/app/views/spree/admin/page_blocks/forms/_brand_logo.html.erb +4 -0
  97. data/app/views/spree/admin/page_blocks/forms/_buttons.html.erb +13 -0
  98. data/app/views/spree/admin/page_blocks/forms/_heading.html.erb +28 -0
  99. data/app/views/spree/admin/page_blocks/forms/_image.html.erb +10 -0
  100. data/app/views/spree/admin/page_blocks/forms/_link.html.erb +19 -0
  101. data/app/views/spree/admin/page_blocks/forms/_mega_nav.html.erb +14 -0
  102. data/app/views/spree/admin/page_blocks/forms/_mega_nav_with_subcategories.html.erb +9 -0
  103. data/app/views/spree/admin/page_blocks/forms/_metafields.html.erb +35 -0
  104. data/app/views/spree/admin/page_blocks/forms/_nav.html.erb +6 -0
  105. data/app/views/spree/admin/page_blocks/forms/_newsletter_form.html.erb +20 -0
  106. data/app/views/spree/admin/page_blocks/forms/_price.html.erb +15 -0
  107. data/app/views/spree/admin/page_blocks/forms/_share.html.erb +5 -0
  108. data/app/views/spree/admin/page_blocks/forms/_subheading.html.erb +26 -0
  109. data/app/views/spree/admin/page_blocks/forms/_text.html.erb +21 -0
  110. data/app/views/spree/admin/page_blocks/forms/_title.html.erb +20 -0
  111. data/app/views/spree/admin/page_blocks/forms/_variant_picker.html.erb +10 -0
  112. data/app/views/spree/admin/page_blocks/forms/products/_buy_buttons.html.erb +10 -0
  113. data/app/views/spree/admin/page_blocks/forms/products/_description.html.erb +0 -0
  114. data/app/views/spree/admin/page_blocks/forms/products/_quantity_selector.html.erb +10 -0
  115. data/app/views/spree/admin/page_blocks/move_higher.turbo_stream.erb +4 -0
  116. data/app/views/spree/admin/page_blocks/move_lower.turbo_stream.erb +4 -0
  117. data/app/views/spree/admin/page_blocks/show.html.erb +1 -0
  118. data/app/views/spree/admin/page_blocks/update.turbo_stream.erb +6 -0
  119. data/app/views/spree/admin/page_builder/_add_block.html.erb +22 -0
  120. data/app/views/spree/admin/page_builder/_color_palette.html.erb +17 -0
  121. data/app/views/spree/admin/page_builder/_color_picker.html.erb +26 -0
  122. data/app/views/spree/admin/page_builder/_header.html.erb +113 -0
  123. data/app/views/spree/admin/page_builder/_labeled_range_input.html.erb +10 -0
  124. data/app/views/spree/admin/page_builder/_pages_dropdown.html.erb +17 -0
  125. data/app/views/spree/admin/page_builder/_range_input.html.erb +12 -0
  126. data/app/views/spree/admin/page_builder/_sidebar.html.erb +12 -0
  127. data/app/views/spree/admin/page_builder/_sidebar_block.html.erb +30 -0
  128. data/app/views/spree/admin/page_builder/_sidebar_colors.html.erb +86 -0
  129. data/app/views/spree/admin/page_builder/_sidebar_fonts.html.erb +85 -0
  130. data/app/views/spree/admin/page_builder/_sidebar_section.html.erb +44 -0
  131. data/app/views/spree/admin/page_builder/_sidebar_sections.html.erb +38 -0
  132. data/app/views/spree/admin/page_builder/_sidebar_sections_toolbar.html.erb +180 -0
  133. data/app/views/spree/admin/page_links/_form.html.erb +10 -0
  134. data/app/views/spree/admin/page_links/_linkable_type_dropdown.html.erb +15 -0
  135. data/app/views/spree/admin/page_links/_list.html.erb +36 -0
  136. data/app/views/spree/admin/page_links/_refresh_theme_preview.turbo_stream.erb +5 -0
  137. data/app/views/spree/admin/page_links/create.turbo_stream.erb +10 -0
  138. data/app/views/spree/admin/page_links/destroy.turbo_stream.erb +15 -0
  139. data/app/views/spree/admin/page_links/edit.html.erb +27 -0
  140. data/app/views/spree/admin/page_links/update.turbo_stream.erb +14 -0
  141. data/app/views/spree/admin/page_sections/_form_tab_buttons.html.erb +8 -0
  142. data/app/views/spree/admin/page_sections/create.turbo_stream.erb +11 -0
  143. data/app/views/spree/admin/page_sections/destroy.turbo_stream.erb +16 -0
  144. data/app/views/spree/admin/page_sections/edit.html.erb +39 -0
  145. data/app/views/spree/admin/page_sections/fields/_background_color.html.erb +6 -0
  146. data/app/views/spree/admin/page_sections/fields/_border_bottom_width.html.erb +5 -0
  147. data/app/views/spree/admin/page_sections/fields/_border_color.html.erb +7 -0
  148. data/app/views/spree/admin/page_sections/fields/_border_top_width.html.erb +6 -0
  149. data/app/views/spree/admin/page_sections/fields/_bottom_padding.html.erb +5 -0
  150. data/app/views/spree/admin/page_sections/fields/_button.html.erb +15 -0
  151. data/app/views/spree/admin/page_sections/fields/_header_layout.html.erb +26 -0
  152. data/app/views/spree/admin/page_sections/fields/_height.html.erb +6 -0
  153. data/app/views/spree/admin/page_sections/fields/_text_color.html.erb +7 -0
  154. data/app/views/spree/admin/page_sections/fields/_top_padding.html.erb +6 -0
  155. data/app/views/spree/admin/page_sections/forms/_announcement_bar.html.erb +5 -0
  156. data/app/views/spree/admin/page_sections/forms/_brand_story.html.erb +0 -0
  157. data/app/views/spree/admin/page_sections/forms/_breadcrumbs.html.erb +0 -0
  158. data/app/views/spree/admin/page_sections/forms/_collection_banner.html.erb +0 -0
  159. data/app/views/spree/admin/page_sections/forms/_custom_code.html.erb +4 -0
  160. data/app/views/spree/admin/page_sections/forms/_featured_posts.html.erb +33 -0
  161. data/app/views/spree/admin/page_sections/forms/_featured_product.html.erb +4 -0
  162. data/app/views/spree/admin/page_sections/forms/_featured_taxon.html.erb +59 -0
  163. data/app/views/spree/admin/page_sections/forms/_featured_taxons.html.erb +13 -0
  164. data/app/views/spree/admin/page_sections/forms/_footer.html.erb +20 -0
  165. data/app/views/spree/admin/page_sections/forms/_header.html.erb +12 -0
  166. data/app/views/spree/admin/page_sections/forms/_image_banner.html.erb +13 -0
  167. data/app/views/spree/admin/page_sections/forms/_image_with_text.html.erb +28 -0
  168. data/app/views/spree/admin/page_sections/forms/_main_password_footer.html.erb +9 -0
  169. data/app/views/spree/admin/page_sections/forms/_main_password_header.erb +13 -0
  170. data/app/views/spree/admin/page_sections/forms/_newsletter.html.erb +15 -0
  171. data/app/views/spree/admin/page_sections/forms/_page_title.html.erb +4 -0
  172. data/app/views/spree/admin/page_sections/forms/_post_details.html.erb +0 -0
  173. data/app/views/spree/admin/page_sections/forms/_post_grid.html.erb +0 -0
  174. data/app/views/spree/admin/page_sections/forms/_product_details.html.erb +1 -0
  175. data/app/views/spree/admin/page_sections/forms/_product_grid.html.erb +1 -0
  176. data/app/views/spree/admin/page_sections/forms/_related_products.html.erb +32 -0
  177. data/app/views/spree/admin/page_sections/forms/_rich_text.html.erb +0 -0
  178. data/app/views/spree/admin/page_sections/forms/_taxon_banner.html.erb +0 -0
  179. data/app/views/spree/admin/page_sections/forms/_taxon_grid.html.erb +5 -0
  180. data/app/views/spree/admin/page_sections/forms/_video.html.erb +15 -0
  181. data/app/views/spree/admin/page_sections/move_higher.turbo_stream.erb +7 -0
  182. data/app/views/spree/admin/page_sections/move_lower.turbo_stream.erb +7 -0
  183. data/app/views/spree/admin/page_sections/new.html.erb +26 -0
  184. data/app/views/spree/admin/page_sections/remove_attachment.turbo_stream.erb +4 -0
  185. data/app/views/spree/admin/page_sections/restore_design_settings_to_defaults.turbo_stream.erb +4 -0
  186. data/app/views/spree/admin/page_sections/show.html.erb +1 -0
  187. data/app/views/spree/admin/page_sections/update.turbo_stream.erb +10 -0
  188. data/app/views/spree/admin/pages/_form.html.erb +20 -0
  189. data/app/views/spree/admin/pages/_table_header.html.erb +6 -0
  190. data/app/views/spree/admin/pages/_table_row.html.erb +28 -0
  191. data/app/views/spree/admin/pages/edit.html.erb +13 -0
  192. data/app/views/spree/admin/pages/filters.html.erb +7 -0
  193. data/app/views/spree/admin/pages/index.html.erb +11 -0
  194. data/app/views/spree/admin/pages/new.html.erb +1 -0
  195. data/app/views/spree/admin/storefront/edit.html.erb +89 -0
  196. data/app/views/spree/admin/themes/_theme.html.erb +56 -0
  197. data/app/views/spree/admin/themes/_theme_preview_image.html.erb +17 -0
  198. data/app/views/spree/admin/themes/edit.html.erb +14 -0
  199. data/app/views/spree/admin/themes/index.html.erb +80 -0
  200. data/app/views/spree/admin/themes/update.turbo_stream.erb +1 -0
  201. data/config/initializers/spree_admin_navigation.rb +46 -0
  202. data/config/initializers/spree_admin_partials.rb +3 -0
  203. data/config/locales/en.yml +42 -0
  204. data/config/routes.rb +43 -0
  205. data/db/migrate/20250120094216_create_page_builder_models.rb +78 -0
  206. data/db/migrate/20250305121352_remove_page_builder_indices.rb +11 -0
  207. data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +7 -0
  208. data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +10 -0
  209. data/lib/generators/spree/page_builder/install/install_generator.rb +23 -0
  210. data/lib/spree/page_builder/engine.rb +185 -0
  211. data/lib/spree/page_builder/testing_support/factories/page_block_factory.rb +22 -0
  212. data/lib/spree/page_builder/testing_support/factories/page_factory.rb +33 -0
  213. data/lib/spree/page_builder/testing_support/factories/page_link_factory.rb +7 -0
  214. data/lib/spree/page_builder/testing_support/factories/page_section_factory.rb +27 -0
  215. data/lib/spree/page_builder/testing_support/factories/theme_factory.rb +14 -0
  216. data/lib/spree/page_builder/testing_support/factories.rb +3 -0
  217. data/lib/spree/page_builder.rb +4 -0
  218. data/lib/spree_page_builder.rb +1 -0
  219. metadata +288 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aff9ef8a6cddfe9ea8ca9031493f8d182dd557367594a11b68e92327c5938d6c
4
+ data.tar.gz: ac52379c8fefd86e2297d929638a22701bc5c558711f6ff8ef10006078696280
5
+ SHA512:
6
+ metadata.gz: 9797793b83bb8236ad9796e6b865b33458e60963fee4a53c813e4d900e9a4cd0d87d975ae0a4272a04bc77cc4575dfa9a14288abd3cdba5cda7c0bb8cbdafa4e
7
+ data.tar.gz: 19a67653afbc2405963daee8b27aa7220eda4bd079831d65783b3464bb23472d1273ce73e39e784512e605759360e5dfd0f560fca6ad3c3e32b00bdc87f3fa1e
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Spree Page Builder
2
+
3
+ Visual page builder and theme management for Spree Commerce storefronts.
4
+
5
+ ## Overview
6
+
7
+ Spree Page Builder provides a visual drag-and-drop interface for building and customizing storefront pages. It includes:
8
+
9
+ - **Theme Management**: Create and manage multiple themes for your store
10
+ - **Page Builder**: Visual editor for creating custom pages
11
+ - **Page Sections**: Pre-built section components (headers, footers, product grids, etc.)
12
+ - **Page Blocks**: Content blocks within sections (text, images, buttons, etc.)
13
+
14
+ ## Installation
15
+
16
+ Add this line to your Spree application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'spree_page_builder'
20
+ ```
21
+
22
+ Or install it through the storefront gem (recommended):
23
+
24
+ ```ruby
25
+ gem 'spree_storefront'
26
+ ```
27
+
28
+ Then execute:
29
+
30
+ ```bash
31
+ bundle install
32
+ rails spree_page_builder:install:migrations
33
+ rails db:migrate
34
+ ```
35
+
36
+ ## Dependencies
37
+
38
+ - `spree_core` - Core Spree functionality
39
+ - `spree_admin` - Admin dashboard for managing pages and themes
40
+
41
+ ## License
42
+
43
+ Spree Page Builder is released under the [AGPL-3.0-or-later License](LICENSE.md).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/common_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task default: :spec
10
+
11
+ desc "Generates a dummy app for testing"
12
+ task :test_app do
13
+ ENV['LIB_NAME'] = 'spree/page_builder'
14
+
15
+ Rake::Task['common:test_app'].execute(
16
+ install_admin: true,
17
+ )
18
+ end
@@ -0,0 +1,45 @@
1
+ module Spree
2
+ module Admin
3
+ module PageBuilderConcern
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper 'spree/admin/page_builder'
8
+ before_action :set_variables
9
+ end
10
+
11
+ def set_variables
12
+ @theme_preview = current_store.theme_previews.find_by(id: session[:theme_preview_id]) if session[:theme_preview_id].present?
13
+ @theme = @theme_preview.present? ? @theme_preview.parent : current_store.default_theme
14
+ if @theme.present? && session[:page_preview_id].present?
15
+ @page_preview = @theme.page_previews.find_by(id: session[:page_preview_id]) ||
16
+ current_store.page_previews.find_by(id: session[:page_preview_id])
17
+ end
18
+ @page = @page_preview.parent if @page_preview
19
+ end
20
+
21
+ def create_turbo_stream_enabled?
22
+ true
23
+ end
24
+
25
+ def update_turbo_stream_enabled?
26
+ true
27
+ end
28
+
29
+ def default_url_options
30
+ {
31
+ theme_preview_id: session[:theme_preview_id],
32
+ page_preview_id: session[:page_preview_id],
33
+ }
34
+ end
35
+
36
+ def location_after_save
37
+ collection_url
38
+ end
39
+
40
+ def collection_url
41
+ spree.edit_admin_theme_path(@theme, page_id: @page.id)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ module Spree
2
+ module Admin
3
+ module StorefrontBreadcrumbConcern
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ add_breadcrumb_icon 'building-store'
8
+ add_breadcrumb Spree.t('admin.storefront'), :admin_themes_path
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,58 @@
1
+ module Spree
2
+ module Admin
3
+ class PageBlocksController < Spree::Admin::ResourceController
4
+ include Spree::Admin::PageBuilderConcern
5
+
6
+ belongs_to 'spree/page_section', find_by: :id
7
+
8
+ def destroy
9
+ @page_block.destroy
10
+ end
11
+
12
+ def move_higher
13
+ if @page_block.first?
14
+ head :ok
15
+ else
16
+ @page_block.move_higher
17
+ end
18
+ end
19
+
20
+ def move_lower
21
+ if @page_block.last?
22
+ head :ok
23
+ else
24
+ @page_block.move_lower
25
+ end
26
+ end
27
+
28
+ def create
29
+ page_block_type = params.dig(:page_block, :type)
30
+ allowed_type = allowed_types.find { |type| type.to_s == page_block_type }
31
+
32
+ if allowed_type
33
+ @page_block = allowed_type.new
34
+ @page_block.section = @page_section
35
+ @page_block.save!
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def allowed_types
42
+ [
43
+ *Spree.page_builder.page_blocks,
44
+ *parent&.available_blocks_to_add
45
+ ].uniq.sort_by(&:name)
46
+ end
47
+
48
+ def permitted_resource_params
49
+ params.require(:page_block).permit(
50
+ permitted_page_block_attributes +
51
+ [link_attributes: permitted_page_link_attributes + [:id]] +
52
+ @object.preferences.keys.map { |key| "preferred_#{key}" } +
53
+ [preferred_metafield_definition_ids: []]
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ module Spree
2
+ module Admin
3
+ class PageLinksController < ResourceController
4
+ include Spree::Admin::PageBuilderConcern
5
+
6
+ before_action :load_parent
7
+
8
+ create.before :set_defaults
9
+
10
+ def destroy
11
+ @page_link.destroy
12
+ end
13
+
14
+ private
15
+
16
+ def find_resource
17
+ model_class.accessible_by(current_ability, :manage).find(params[:id])
18
+ end
19
+
20
+ def collection_url
21
+ @collection_url ||= if @parent.is_a?(Spree::PageSection)
22
+ spree.edit_admin_page_section_path(@parent)
23
+ elsif @parent.is_a?(Spree::PageBlock)
24
+ spree.edit_admin_page_section_block_path(@parent.section, @parent)
25
+ elsif @parent.is_a?(Spree::Store)
26
+ spree.edit_admin_store_path(section: :checkout)
27
+ end
28
+ end
29
+
30
+ def load_parent
31
+ @parent ||= if @page_link&.persisted?
32
+ @page_link.parent
33
+ elsif params[:block_id].present?
34
+ Spree::PageBlock.find(params[:block_id])
35
+ elsif params[:page_section_id].present?
36
+ Spree::PageSection.find(params[:page_section_id])
37
+ elsif params[:store_id].present?
38
+ current_store
39
+ end
40
+ end
41
+
42
+ def set_defaults
43
+ raise ActiveRecord::RecordNotFound unless @parent
44
+
45
+ @page_link.parent = @parent
46
+ @page_link.linkable_type ||= @parent.default_linkable_type
47
+ @page_link.linkable ||= @parent.default_linkable_resource
48
+ end
49
+
50
+ # for create action we don't pass page_link at all
51
+ def permitted_resource_params
52
+ if params[:page_link].present?
53
+ params.require(:page_link).permit(permitted_page_link_attributes)
54
+ else
55
+ {}
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,82 @@
1
+ module Spree
2
+ module Admin
3
+ class PageSectionsController < ResourceController
4
+ include Spree::Admin::PageBuilderConcern
5
+
6
+ before_action :load_pageable, only: %i[new create index]
7
+
8
+ def create
9
+ page_section_type = params.dig(:page_section, :type)
10
+ allowed_types = available_page_section_types.map(&:to_s)
11
+
12
+ if allowed_types.include?(page_section_type)
13
+ # Find the actual class from our allowed types rather than using constantize
14
+ section_class = available_page_section_types.find { |type| type.to_s == page_section_type }
15
+
16
+ if section_class
17
+ @page_section = section_class.new(permitted_resource_params)
18
+ @page_section.pageable = @pageable
19
+ @page_section.save!
20
+ end
21
+ end
22
+ end
23
+
24
+ def destroy
25
+ @page_section.destroy if @page_section.can_be_deleted?
26
+ end
27
+
28
+ def move_higher
29
+ if @page_section.first?
30
+ head :ok
31
+ else
32
+ @page_section.move_higher
33
+ end
34
+ end
35
+
36
+ def move_lower
37
+ if @page_section.last?
38
+ head :ok
39
+ else
40
+ @page_section.move_lower
41
+ end
42
+ end
43
+
44
+ def restore_design_settings_to_defaults
45
+ @page_section.restore_design_settings_to_defaults
46
+ end
47
+
48
+ private
49
+
50
+ def load_pageable
51
+ @pageable = if @page_section.present? && @page_section.pageable.present?
52
+ @page_section.pageable
53
+ elsif params[:page_id].present?
54
+ current_store.theme_page_previews.friendly.find_by(id: params[:page_id]) ||
55
+ current_store.page_previews.friendly.find(params[:page_id])
56
+ elsif params[:theme_id].present?
57
+ current_store.theme_previews.find(params[:theme_id])
58
+ end
59
+ end
60
+
61
+ def available_page_section_types
62
+ return [] unless @pageable.present?
63
+
64
+ if @pageable.is_a?(Spree::Theme)
65
+ @pageable.available_page_sections
66
+ elsif @pageable.respond_to?(:theme)
67
+ @pageable.theme.available_page_sections
68
+ else
69
+ []
70
+ end
71
+ end
72
+
73
+ def permitted_resource_params
74
+ params.require(:page_section).permit(
75
+ permitted_page_section_attributes +
76
+ [link_attributes: permitted_page_link_attributes + [:id]] +
77
+ @object.preferences.keys.map { |key| "preferred_#{key}" }
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ module Spree
2
+ module Admin
3
+ class PagesController < Spree::Admin::ResourceController
4
+ create.before :set_page
5
+ update.after :remove_preview_from_session
6
+ destroy.after :remove_preview_from_session
7
+ create.after :remove_preview_from_session
8
+
9
+ include StorefrontBreadcrumbConcern
10
+ add_breadcrumb Spree.t(:pages), :admin_pages_path
11
+
12
+ private
13
+
14
+ def remove_preview_from_session
15
+ session.delete(:page_preview_id)
16
+ end
17
+
18
+ def flash_message_for(object, event_sym)
19
+ Spree.t(event_sym, resource: "#{Spree.t(:page)} \"#{object.name}\"")
20
+ end
21
+
22
+ def set_page
23
+ @object = current_store.pages.custom.new
24
+ end
25
+
26
+ def edit_object_url(object, options = {})
27
+ spree.edit_admin_theme_path(current_store.default_theme, page_id: object.id, **options)
28
+ end
29
+
30
+ def scope
31
+ super.custom.without_previews
32
+ end
33
+
34
+ def permitted_resource_params
35
+ params.require(:page).permit(permitted_page_attributes)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Admin
3
+ class StorefrontController < BaseController
4
+ include StorefrontBreadcrumbConcern
5
+ add_breadcrumb Spree.t(:settings), :edit_admin_storefront_path
6
+
7
+ def edit
8
+ @store = current_store
9
+ end
10
+
11
+ def update
12
+ @store = current_store
13
+ if @store.update(store_params)
14
+ remove_assets(%w[favicon_image social_image], object: @store)
15
+ flash[:success] = flash_message_for(@store, :successfully_updated)
16
+ else
17
+ flash[:error] = @store.errors.full_messages.to_sentence
18
+ end
19
+
20
+ redirect_to spree.edit_admin_storefront_path
21
+ end
22
+
23
+ private
24
+
25
+ def store_params
26
+ params.require(:store).permit(
27
+ :preferred_index_in_search_engines,
28
+ :preferred_password_protected, :social_image, :favicon_image, :name,
29
+ :meta_description, :meta_keywords, :seo_robots, :seo_title,
30
+ :facebook, :twitter, :instagram, :linkedin, :youtube, :tiktok, :pinterest,
31
+ :storefront_custom_code_head, :storefront_custom_code_body_start,
32
+ :storefront_custom_code_body_end, :storefront_password, :spotify, :discord
33
+ )
34
+ end
35
+
36
+ def model_class
37
+ Spree::Store
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,87 @@
1
+ module Spree
2
+ module Admin
3
+ class ThemesController < ResourceController
4
+ layout :choose_layout
5
+
6
+ include StorefrontBreadcrumbConcern
7
+ helper 'spree/admin/page_builder'
8
+ add_breadcrumb Spree.t(:themes), :admin_themes_path
9
+
10
+ def edit
11
+ @theme_preview = params[:theme_preview_id].present? ? @theme.previews.find(params[:theme_preview_id]) : @theme.create_preview
12
+ @page = if params[:page_id].present?
13
+ @theme.pages.find_by(id: params[:page_id]) || current_store.pages.find(params[:page_id])
14
+ else
15
+ @theme.pages.find_by(type: 'Spree::Pages::Homepage')
16
+ end
17
+ @page_preview = params[:page_preview_id].present? ? @page.previews.find(params[:page_preview_id]) : @page.create_preview
18
+
19
+ session[:theme_preview_id] = @theme_preview.id
20
+ session[:page_preview_id] = @page_preview.id
21
+ end
22
+
23
+ def update_with_page
24
+ @theme_preview = @theme.previews.find(params[:theme_preview_id])
25
+ @page_preview = @theme.page_previews.find_by(id: params[:page_preview_id]) || current_store.page_previews.find(params[:page_preview_id])
26
+
27
+ ApplicationRecord.transaction do
28
+ @page_preview.promote && @theme_preview.promote
29
+ end
30
+ flash[:success] = Spree.t('changes_published')
31
+
32
+ redirect_to spree.edit_admin_theme_path(@theme_preview, page_id: @page_preview.id), status: :see_other
33
+ rescue ActiveRecord::RecordInvalid
34
+ redirect_to spree.edit_admin_theme_path(@theme_preview, page_id: @page_preview.id), alert: Spree.t('something_went_wrong')
35
+ end
36
+
37
+ def publish
38
+ @theme = current_store.themes.find(params[:id])
39
+
40
+ if @theme.update(default: true)
41
+ flash[:success] = Spree.t('theme_is_now_live')
42
+ else
43
+ flash[:error] = "#{Spree.t('something_went_wrong')}: #{@theme.errors.full_messages.to_sentence}"
44
+ end
45
+ redirect_to spree.admin_themes_path
46
+ end
47
+
48
+ def clone
49
+ @new = @theme.duplicate
50
+
51
+ if @new.persisted?
52
+ flash[:success] = Spree.t('theme_copied')
53
+ else
54
+ flash[:error] = Spree.t('theme_not_copied', error: @new.errors.full_messages.to_sentence)
55
+ end
56
+
57
+ redirect_to spree.admin_themes_path
58
+ end
59
+
60
+ private
61
+
62
+ def choose_layout
63
+ if action_name == 'index'
64
+ 'spree/admin'
65
+ else
66
+ 'spree/page_builder'
67
+ end
68
+ end
69
+
70
+ def update_turbo_stream_enabled?
71
+ true
72
+ end
73
+
74
+ def scope
75
+ super.without_previews.order(default: :desc)
76
+ end
77
+
78
+ def collection_includes
79
+ { screenshot_attachment: :blob }
80
+ end
81
+
82
+ def permitted_resource_params
83
+ params.require(:theme).permit(permitted_theme_attributes + @object.preferences.keys.map { |key| "preferred_#{key}" })
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,125 @@
1
+ module Spree
2
+ module Admin
3
+ module PageBuilderHelper
4
+ def system_fonts_list
5
+ ['Sans-serif', 'Serif', 'Mono']
6
+ end
7
+
8
+ def google_fonts_list
9
+ ['Abel', 'Abril Fatface', 'Alegreya', 'Alegreya Sans', 'Alice', 'Amiri', 'Anonymous Pro', 'Arapey', 'Archivo', 'Archivo Narrow',
10
+ 'Arimo', 'Armata', 'Arvo', 'Asap', 'Assistant', 'Asul', 'Bitter', 'Cabin', 'Cardo', 'Catamaran', 'Chivo', 'Cormorant',
11
+ 'Crimson Text', 'DM Sans', 'Domine', 'Dosis', 'Eczar', 'Figtree', 'Fira Sans', 'Fjalla One', 'Glegoo', 'IBM Plex Sans', 'Inconsolata',
12
+ 'Inknut Antiqua', 'Inter', 'Josefin Sans', 'Josefin Slab', 'Kalam', 'Karla', 'Kreon', 'Lato', 'Libre Baskerville',
13
+ 'Libre Franklin', 'Lobster', 'Lobster Two', 'Lora', 'Maven Pro', 'Megrim', 'Merriweather Sans', 'Montserrat',
14
+ 'Mouse Memoirs', 'Muli', 'Neuton', 'News Cycle', 'Nobile', 'Noticia Text', 'Noto Serif', 'Nunito', 'Nunito Sans',
15
+ 'Old Standard TT', 'Open Sans', 'Oswald', 'Ovo', 'Oxygen', 'PT Mono', 'PT Sans', 'PT Sans Narrow', 'PT Serif',
16
+ 'Pacifico', 'Playball', 'Playfair Display', 'Poppins', 'Prata', 'Prompt', 'Quattrocento', 'Quattrocento Sans',
17
+ 'Questrial', 'Quicksand', 'Rajdhani', 'Raleway', 'Righteous', 'Roboto', 'Roboto Condensed', 'Roboto Mono', 'Roboto Slab',
18
+ 'Rubik', 'Shadows Into Light', 'Slabo 13px', 'Slabo 27px', 'Smooch', 'Source Code Pro', 'Source Sans Pro', 'Space Mono',
19
+ 'Syne', 'Tenor Sans', 'Tinos', 'Titillium Web', 'Ubuntu', 'Unica One', 'Unna', 'Varela', 'Varela Round', 'Vidaloka',
20
+ 'Volkhov', 'Vollkorn', 'Work Sans']
21
+ end
22
+
23
+ def color_palettes
24
+ [
25
+ {
26
+ primary_color: '#1e4bd1',
27
+ text_color: '#000000',
28
+ border_color: '#d1d5db',
29
+ button_text_color: '#ffffff',
30
+ background_color: '#F9FAFB'
31
+ },
32
+ {
33
+ primary_color: '#e63946',
34
+ text_color: '#1d3557',
35
+ border_color: '#a8dadc',
36
+ button_text_color: '#ffffff',
37
+ background_color: '#f1faee'
38
+ },
39
+ {
40
+ primary_color: '#264653',
41
+ text_color: '#060C0E',
42
+ border_color: '#2a9d8f',
43
+ button_text_color: '#ffffff',
44
+ background_color: '#e9c46a'
45
+ },
46
+ {
47
+ primary_color: '#606c38',
48
+ text_color: '#283618',
49
+ border_color: '#dda15e',
50
+ button_text_color: '#ffffff',
51
+ background_color: '#fefae0'
52
+ },
53
+ {
54
+ primary_color: '#FF961F',
55
+ text_color: '#023047',
56
+ border_color: '#219EBC',
57
+ button_text_color: '#FFEEDA',
58
+ background_color: '#DEF0F7'
59
+ },
60
+ {
61
+ primary_color: '#181CF1',
62
+ text_color: '#011627',
63
+ border_color: '#7678ED',
64
+ button_text_color: '#FFFFFF',
65
+ background_color: '#FFF5D6'
66
+ }
67
+ ]
68
+ end
69
+
70
+ def color_palette_active?(color_palette)
71
+ @theme_preview&.primary_color&.downcase == color_palette[:primary_color].downcase &&
72
+ @theme_preview&.text_color&.downcase == color_palette[:text_color].downcase &&
73
+ @theme_preview&.border_color&.downcase == color_palette[:border_color].downcase &&
74
+ @theme_preview&.button_text_color&.downcase == color_palette[:button_text_color].downcase &&
75
+ @theme_preview&.background_color&.downcase == color_palette[:background_color].downcase
76
+ end
77
+
78
+ def all_fonts_options
79
+ @all_fonts_options ||= system_fonts_list + google_fonts_list
80
+ end
81
+
82
+ def all_linkable_pages
83
+ @all_linkable_pages ||= Spree::Page.
84
+ without_previews.
85
+ where(pageable: [@theme, current_store]).
86
+ select(&:linkable?).
87
+ map { |page| [page.display_name, page.id] }.sort_by(&:first)
88
+ end
89
+
90
+ def all_linkable_policies
91
+ @all_linkable_policies ||= current_store.policies.pluck(:name, :id)
92
+ end
93
+
94
+ def refresh_theme_preview(section = nil, block = nil)
95
+ reload_params = section.present? ? "{ frame: 'section-#{section.id}' }" : "{ action: 'replace' }"
96
+
97
+ editor_id = if block.present?
98
+ "block-#{block.id}"
99
+ elsif section.present?
100
+ "section-#{section.id}"
101
+ end
102
+
103
+ turbo_stream.replace :iframe_preview_scripts do
104
+ turbo_frame_tag :iframe_preview_scripts do
105
+ javascript_tag do
106
+ raw <<~JS
107
+ document.getElementById('page-builder-preview').contentWindow.window.Turbo.visit(
108
+ document.getElementById('page-builder').dataset.pageBuilderPreviewUrlValue + '&editor_id=#{editor_id}',
109
+ #{reload_params}
110
+ )
111
+ JS
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def page_preview_url
118
+ preview_url = @page.preview_url(@theme_preview, @page_preview)
119
+ return if preview_url.blank?
120
+
121
+ "#{preview_url}&page_builder=true"
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Admin
3
+ module ThemesHelper
4
+ def available_themes
5
+ @available_themes ||= Spree::Theme.available_themes.sort_by(&:name).reject { |theme_class| current_store.themes.any? { |theme| theme.class.to_s == theme_class.to_s } }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module PageBuilder
3
+ module Products
4
+ module TouchTaxonsJobDecorator
5
+ def perform(taxon_ids, taxonomy_ids)
6
+ super(taxon_ids, taxonomy_ids)
7
+
8
+ Spree::Taxons::TouchFeaturedSections.call(taxon_ids: taxon_ids)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Spree::Products::TouchTaxonsJob.prepend(Spree::PageBuilder::Products::TouchTaxonsJobDecorator)