spree_easy_homepage 1.0.0

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +34 -0
  5. data/.travis.yml +51 -0
  6. data/Appraisals +28 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE +26 -0
  9. data/README.md +124 -0
  10. data/Rakefile +22 -0
  11. data/app/assets/javascripts/spree/backend/product_autosearch.js +33 -0
  12. data/app/assets/javascripts/spree/backend/spree_easy_homepage.js +42 -0
  13. data/app/assets/javascripts/spree/frontend/spree_easy_homepage.js +2 -0
  14. data/app/assets/stylesheets/spree/backend/spree_easy_homepage.scss +12 -0
  15. data/app/concerns/spree/product_ids_processable.rb +19 -0
  16. data/app/controllers/spree/admin/base_controller_decorator.rb +13 -0
  17. data/app/controllers/spree/admin/home_sections_controller.rb +6 -0
  18. data/app/helpers/spree/admin/home_section_helper.rb +28 -0
  19. data/app/helpers/spree/easy_homepage_helper.rb +29 -0
  20. data/app/models/spree/home_section.rb +44 -0
  21. data/app/models/spree/home_section_product.rb +8 -0
  22. data/app/models/spree/product_decorator.rb +29 -0
  23. data/app/overrides/spree/admin/shared/sub_menu/_configuration.rb +8 -0
  24. data/app/presenter/spree/easy_homepage/product_presenter.rb +40 -0
  25. data/app/views/spree/admin/home_section_products/_line_item.js.erb +27 -0
  26. data/app/views/spree/admin/home_sections/_form.html.erb +48 -0
  27. data/app/views/spree/admin/home_sections/edit.html.erb +11 -0
  28. data/app/views/spree/admin/home_sections/index.html.erb +52 -0
  29. data/app/views/spree/admin/home_sections/new.html.erb +11 -0
  30. data/app/views/spree/admin/products/_autosearch.js.erb +16 -0
  31. data/app/views/spree/shared/_home_sections.html.erb +16 -0
  32. data/bin/rails +8 -0
  33. data/config/locales/en.yml +16 -0
  34. data/config/routes.rb +9 -0
  35. data/db/migrate/20191228110047_create_spree_home_section.rb +10 -0
  36. data/db/migrate/20191228112655_create_spree_home_section_product.rb +11 -0
  37. data/gemfiles/spree_3_5.gemfile +10 -0
  38. data/gemfiles/spree_3_7.gemfile +11 -0
  39. data/gemfiles/spree_4_0.gemfile +10 -0
  40. data/gemfiles/spree_master.gemfile +10 -0
  41. data/lib/generators/spree_easy_homepage/install/install_generator.rb +29 -0
  42. data/lib/spree_easy_homepage.rb +4 -0
  43. data/lib/spree_easy_homepage/engine.rb +20 -0
  44. data/lib/spree_easy_homepage/version.rb +17 -0
  45. data/spree_easy_homepage.gemspec +47 -0
  46. metadata +408 -0
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module ProductIdsProcessable
3
+ extend ActiveSupport::Concern
4
+
5
+ def add_product(product_id:)
6
+ products << Spree::Product.find(product_id)
7
+ end
8
+
9
+ def add_products(product_ids:)
10
+ clean_ids(product_ids).each { |id| add_product(product_id: id) }
11
+ end
12
+
13
+ protected
14
+
15
+ def clean_ids(ids)
16
+ ids.reject(&:blank?)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module Admin
3
+ module BaseControllerDecorator
4
+ Spree::Admin::BaseController.include(Spree::Admin::HomeSectionHelper)
5
+
6
+ def self.prepended(base)
7
+ base.helper_method :parse_home_sections
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ Spree::Admin::BaseController.prepend Spree::Admin::BaseControllerDecorator
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ module Admin
3
+ class HomeSectionsController < ResourceController
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ module Admin
3
+ module HomeSectionHelper
4
+ def parse_home_sections(home_section)
5
+ return [] unless home_section.section_products?
6
+
7
+ parse_products(products: home_section.products_by_position)
8
+ end
9
+
10
+ def parse_products(products:)
11
+ products.map { |product| product_presenter(product: product).as_json }
12
+ end
13
+
14
+ def product_presenter(product:)
15
+ presenter(product).parse
16
+ end
17
+
18
+ private
19
+
20
+ def presenter(product)
21
+ Spree::EasyHomepage::ProductPresenter.new(
22
+ product: product,
23
+ main_app: main_app
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ module Spree
2
+ module EasyHomepageHelper
3
+ def fill_with_sections(partial: home_section_partial, **partial_args)
4
+ get_homesection_partial(partial: partial, **partial_args)
5
+ end
6
+
7
+ def home_sections
8
+ Spree::HomeSection.all
9
+ end
10
+
11
+ private
12
+
13
+ def get_homesection_partial(partial: home_section_partial, **partial_args)
14
+ render(partial: partial, **partial_args)
15
+ end
16
+
17
+ def home_section_partial
18
+ 'spree/shared/home_sections'
19
+ end
20
+ end
21
+ end
22
+
23
+ # Author note:
24
+ # This helper and the homesection partial view, violates a few OOP principles
25
+ # I plan to change this when I have time to do so.
26
+ # Although I don't plan to change the public interface,
27
+ # the private interface will.
28
+ # for example, instead of calling render,
29
+ # a new Presenter or Service object would instead be called
@@ -0,0 +1,44 @@
1
+ module Spree
2
+ class HomeSection < Spree::Base
3
+ include Spree::ProductIdsProcessable
4
+ acts_as_list
5
+
6
+ has_many :home_section_products, dependent: :destroy
7
+ has_many :products, through: :home_section_products, source: :product
8
+
9
+ alias_attribute :sections, :home_section_products
10
+
11
+ after_save :reset_section_products
12
+
13
+ attr_accessor :product_ids, :force_delete
14
+
15
+ def delete_sections
16
+ home_section_products.delete_all
17
+ end
18
+
19
+ def product_ids?
20
+ product_ids.present?
21
+ end
22
+
23
+ def force_delete?
24
+ force_delete.present?
25
+ end
26
+
27
+ def products_by_position
28
+ products.order('spree_home_section_products.position ASC')
29
+ end
30
+
31
+ def section_products?
32
+ home_section_products.any?
33
+ end
34
+
35
+ protected
36
+
37
+ def reset_section_products
38
+ delete_sections if force_delete?
39
+ return unless product_ids?
40
+
41
+ add_products(product_ids: product_ids)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ module Spree
2
+ class HomeSectionProduct < Spree::Base
3
+ acts_as_list scope: :home_section
4
+
5
+ belongs_to :home_section, class_name: 'Spree::HomeSection', required: true
6
+ belongs_to :product, class_name: 'Spree::Product', required: true
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ module Spree
2
+ module ProductDecorator
3
+ def self.prepended(base)
4
+ base.has_many :home_section_products,
5
+ class_name: 'Spree::HomeSectionProduct'
6
+
7
+ base.has_many :home_sections,
8
+ through: :home_section_products,
9
+ class_name: 'Spree::HomeSection'
10
+ end
11
+
12
+ def image_url(style: :product)
13
+ image = images.first
14
+ return if image.blank?
15
+
16
+ image.url(style)
17
+ end
18
+
19
+ def presenter_attributes
20
+ {
21
+ id: id,
22
+ name: name,
23
+ image: image_url(style: :mini)
24
+ }
25
+ end
26
+ end
27
+ end
28
+
29
+ Spree::Product.prepend Spree::ProductDecorator
@@ -0,0 +1,8 @@
1
+ Deface::Override.new(
2
+ virtual_path: 'spree/admin/shared/sub_menu/_configuration',
3
+ name: 'home_section_menu_item',
4
+ insert_after: "erb[loud]:contains('configurations_sidebar_menu_item(Spree.t(:general_settings), spree.edit_admin_general_settings_path) if can? :manage, Spree::Config')",
5
+ text: "<%= configurations_sidebar_menu_item(
6
+ Spree.t(:home_section_menu_name),
7
+ spree.admin_home_sections_path) if can? :manage, Spree::HomeSection %>"
8
+ )
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ module EasyHomepage
3
+ class ProductPresenter
4
+ include Rails.application.routes.url_helpers
5
+ include Spree::Core::Engine.routes.url_helpers
6
+
7
+ attr_accessor :product
8
+ attr_reader :main_app
9
+
10
+ def initialize(product:, main_app:)
11
+ @product = product
12
+ @main_app = main_app
13
+ end
14
+
15
+ def parse
16
+ product_attributes
17
+ end
18
+
19
+ protected
20
+
21
+ def image_link(image_url)
22
+ return if image_url.nil?
23
+
24
+ main_app.url_for(image_url)
25
+ end
26
+
27
+ def path
28
+ edit_admin_product_path(product)
29
+ end
30
+
31
+ private
32
+
33
+ def product_attributes
34
+ parser_attributes = product.presenter_attributes
35
+ parser_attributes[:image] = image_link(parser_attributes[:image])
36
+ parser_attributes.merge(path: path)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ <script type='text/template' id='product-search-lineitem'>
2
+ <tr data-hook='home_sections_products_row'>
3
+ <input name="home_section[product_ids][]" type="hidden" value="{{product.id}}" />
4
+ <td class='move-handle handle grabbable'>
5
+ <span class='icon icon-move'></span>
6
+ </td>
7
+ <td>
8
+ {{#if product.image }}
9
+ <img src='{{product.image}}' class='thumbnail mb-0' />
10
+ {{ else }}
11
+ <img src='<%= image_path('noimage/mini.png') %>' class='thumbnail mb-0' />
12
+ {{/if}}
13
+ </td>
14
+ <td>
15
+ <strong></strong>
16
+ {{#if product.path }}
17
+ <a target='_blank' href='{{product.path}}'>{{product.name}}</a>
18
+ {{else}}
19
+ {{product.name}}
20
+ {{/if}}
21
+ </td>
22
+ <td class='actions actions-2 text-right'>
23
+ <%= link_to_delete('#', {no_text: true, url: '#', data: {}}) %>
24
+ </td>
25
+ </tr>
26
+ </script>
27
+
@@ -0,0 +1,48 @@
1
+ <%= render partial: 'spree/shared/error_messages', locals: { target: @home_section } %>
2
+
3
+ <%= render partial: 'spree/admin/home_section_products/line_item', formats: :js %>
4
+ <%= render partial: 'spree/admin/products/autosearch', formats: :js %>
5
+
6
+ <div class="form-group">
7
+ <%= f.field_container :title, class: ['form-group'] do %>
8
+ <%= f.label :title %>
9
+ <%= f.text_field :title, class: 'form-control title' %>
10
+ <%= f.error_message_on :title %>
11
+ <% end %>
12
+ </div>
13
+
14
+ <div class="form-group">
15
+ <table class="table">
16
+ <thead data-hook="home_sections_product_header">
17
+ <tr>
18
+ <th></th>
19
+ <th><%= Spree.t(:image) %></th>
20
+ <th><%= plural_resource_name(Spree::Product) %></th>
21
+ <th class="actions"></th>
22
+ </tr>
23
+ </thead>
24
+ <%= content_tag :tbody,
25
+ id: "section-product-list",
26
+ data: {
27
+ home_sections: parse_home_sections(@home_section)
28
+ } do %>
29
+
30
+ <% end %>
31
+ </table>
32
+ </div>
33
+
34
+ <%= hidden_field_tag 'home_section[force_delete]', true %>
35
+
36
+ <div class="card mb-3">
37
+ <div class="card-header">
38
+ <h1 class="card-title mb-0 h5"><%= Spree.t(:add_product) %></h1>
39
+ </div>
40
+ <div class="card-body">
41
+ <div class="form-group">
42
+ <%= label_tag :product_search, Spree.t(:name_or_sku) %>
43
+ <%= hidden_field_tag :product_search, "",
44
+ class: "product-autosearch d-block w-100"
45
+ %>
46
+ </div>
47
+ </div>
48
+ </div>
@@ -0,0 +1,11 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to Spree.t(plural_resource_name(Spree::HomeSection)), spree.admin_home_sections_url %> /
3
+ <%= @home_section.title %>
4
+ <% end %>
5
+
6
+ <%= form_for [:admin, @home_section] do |f| %>
7
+ <fieldset>
8
+ <%= render partial: 'form', locals: { f: f } %>
9
+ <%= render partial: 'spree/admin/shared/edit_resource_links' %>
10
+ </fieldset>
11
+ <% end %>
@@ -0,0 +1,52 @@
1
+ <% content_for :page_title do %>
2
+ <%= plural_resource_name(Spree::HomeSection) %>
3
+ <% end %>
4
+
5
+ <% content_for :page_actions do %>
6
+ <%= button_link_to Spree.t(:new_home_section), new_object_url, { class: "btn-success", icon: 'add', id: 'admin_new_home_section' } %>
7
+ <% end if can?(:create, Spree::HomeSection) %>
8
+
9
+ <% if @home_sections.any? %>
10
+ <table class="table sortable" data-sortable-link="<%= update_positions_admin_home_sections_url %>">
11
+ <thead data-hook="home_sections_header">
12
+ <tr>
13
+ <th></th>
14
+ <th><%= I18n.t(:title, scope: 'activerecord.attributes.spree/home_section') %></th>
15
+ <th><%= plural_resource_name(Spree::Product) %></th>
16
+ <th class="actions"></th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @home_sections.each do |home_section| %>
21
+ <tr id="<%= spree_dom_id home_section %>" data-hook="home_sections_row">
22
+ <td class="move-handle handle grabbable">
23
+ <% if can? :edit, home_section %>
24
+ <span class="icon icon-move"></span>
25
+ <% end %>
26
+ </td>
27
+ <td><%= home_section.title %></td>
28
+ <td>
29
+ <%= home_section.products.count %>
30
+ <%= I18n.t('activerecord.models.spree/product', count: home_section.products.count) %>
31
+ <%= '-' if home_section.products.any? %>
32
+ <% home_section.products_by_position.each_with_index do |product, index| %>
33
+ <%= link_to "\"#{product.try(:name)}\"", edit_admin_product_path(product), target: '_blank' %>
34
+ <%= ', ' if index < home_section.products.count - 1 %>
35
+ <% end %>
36
+ </td>
37
+ <td class="actions actions-2 text-right">
38
+ <%= link_to_edit(home_section, no_text: true) if can?(:edit, home_section) %>
39
+ <%= link_to_delete(home_section, no_text: true) if can?(:destroy, home_section) %>
40
+ </td>
41
+ </tr>
42
+ <% end %>
43
+ </tbody>
44
+ </table>
45
+ <% else %>
46
+ <div class="no-objects-found alert alert-info">
47
+ <%= Spree.t(:no_resource_found, resource: plural_resource_name(Spree::HomeSection)) %>
48
+ <% if can?(:create, Spree::HomeSection) %>
49
+ - <%= link_to(Spree.t(:add_one), spree.new_admin_home_section_path(@home_section)) %>!
50
+ <% end %>
51
+ </div>
52
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to plural_resource_name(Spree::HomeSection), spree.admin_home_sections_url %> /
3
+ <%= Spree.t(:new_home_section) %>
4
+ <% end %>
5
+
6
+ <%= form_for [:admin, @home_section] do |f| %>
7
+ <fieldset>
8
+ <%= render partial: 'form', locals: { f: f } %>
9
+ <%= render partial: 'spree/admin/shared/new_resource_links' %>
10
+ </fieldset>
11
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <script type='text/template' id='product-search-result'>
2
+ <div class='variant-autocomplete-item media align-items-center'>
3
+ <figure class='variant-image media-left mb-0 mr-3'>
4
+ {{#if product.image }}
5
+ <img src='{{product.image}}' class="thumbnail mb-0" />
6
+ {{ else }}
7
+ <img src='<%= image_path("noimage/mini.png") %>' class="thumbnail mb-0" />
8
+ {{/if}}
9
+ </figure>
10
+
11
+ <div class='variant-details media-body'>
12
+ <h5 class="variant-name media-heading h6 mb-0">{{product.name}}</h5>
13
+ <strong><%= Spree.t(:sku) %>:</strong> {{product.sku}}
14
+ </div>
15
+ </div>
16
+ </script>
@@ -0,0 +1,16 @@
1
+ <% product_partial ||= 'spree/products/product' %>
2
+ <% product_locals ||= {} %>
3
+
4
+ <% home_sections.each do |section| %>
5
+ <div class="row text-center">
6
+ <div class="col-sm-12">
7
+ <h2> <%= section.title %> </h2>
8
+ </div>
9
+ </div>
10
+ <div class="row">
11
+ <% section.products_by_position.each do |product| %>
12
+ <% locals = {product: product, taxon: @taxon || nil}.merge(product_locals) %>
13
+ <%= render partial: product_partial, locals: locals %>
14
+ <% end %>
15
+ </div>
16
+ <% end %>
data/bin/rails ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" from the root of your extension
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/spree_easy_homepage/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'