spree_easy_homepage 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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'