workarea-variant_list 1.0.2

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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  4. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.gitignore +22 -0
  7. data/CHANGELOG.md +49 -0
  8. data/CODE_OF_CONDUCT.md +3 -0
  9. data/CONTRIBUTING.md +3 -0
  10. data/Gemfile +10 -0
  11. data/LICENSE +52 -0
  12. data/README.md +32 -0
  13. data/Rakefile +60 -0
  14. data/app/assets/javascripts/workarea/storefront/variant_list/modules/sortable_tables.js +107 -0
  15. data/app/assets/stylesheets/workarea/storefront/variant_list/components/_product_details.scss +40 -0
  16. data/app/controllers/workarea/storefront/cart_bulk_items_controller.rb +30 -0
  17. data/app/view_models/workarea/storefront/product_templates/variant_list_view_model.rb +20 -0
  18. data/app/view_models/workarea/storefront/variant_view_model.rb +27 -0
  19. data/app/views/workarea/storefront/cart_bulk_items/create.html.haml +71 -0
  20. data/app/views/workarea/storefront/products/templates/_variant_list.html.haml +82 -0
  21. data/bin/rails +20 -0
  22. data/config/initializers/appends.rb +9 -0
  23. data/config/initializers/config.rb +4 -0
  24. data/config/locales/en.yml +11 -0
  25. data/config/routes.rb +7 -0
  26. data/lib/workarea/variant_list.rb +11 -0
  27. data/lib/workarea/variant_list/engine.rb +8 -0
  28. data/lib/workarea/variant_list/version.rb +5 -0
  29. data/test/dummy/Rakefile +6 -0
  30. data/test/dummy/app/assets/config/manifest.js +4 -0
  31. data/test/dummy/app/assets/images/.keep +0 -0
  32. data/test/dummy/app/assets/javascripts/application.js +13 -0
  33. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  34. data/test/dummy/app/controllers/application_controller.rb +3 -0
  35. data/test/dummy/app/controllers/concerns/.keep +0 -0
  36. data/test/dummy/app/helpers/application_helper.rb +2 -0
  37. data/test/dummy/app/jobs/application_job.rb +2 -0
  38. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  39. data/test/dummy/app/models/concerns/.keep +0 -0
  40. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  41. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  42. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  43. data/test/dummy/bin/bundle +3 -0
  44. data/test/dummy/bin/rails +4 -0
  45. data/test/dummy/bin/rake +4 -0
  46. data/test/dummy/bin/setup +38 -0
  47. data/test/dummy/bin/update +29 -0
  48. data/test/dummy/bin/yarn +11 -0
  49. data/test/dummy/config.ru +5 -0
  50. data/test/dummy/config/application.rb +28 -0
  51. data/test/dummy/config/boot.rb +5 -0
  52. data/test/dummy/config/cable.yml +10 -0
  53. data/test/dummy/config/environment.rb +5 -0
  54. data/test/dummy/config/environments/development.rb +54 -0
  55. data/test/dummy/config/environments/production.rb +91 -0
  56. data/test/dummy/config/environments/test.rb +44 -0
  57. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  58. data/test/dummy/config/initializers/assets.rb +14 -0
  59. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  61. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/test/dummy/config/initializers/inflections.rb +16 -0
  63. data/test/dummy/config/initializers/mime_types.rb +4 -0
  64. data/test/dummy/config/initializers/workarea.rb +5 -0
  65. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  66. data/test/dummy/config/locales/en.yml +33 -0
  67. data/test/dummy/config/puma.rb +56 -0
  68. data/test/dummy/config/routes.rb +5 -0
  69. data/test/dummy/config/secrets.yml +32 -0
  70. data/test/dummy/config/spring.rb +6 -0
  71. data/test/dummy/db/seeds.rb +2 -0
  72. data/test/dummy/lib/assets/.keep +0 -0
  73. data/test/dummy/log/.keep +0 -0
  74. data/test/integration/workarea/storefront/cart_bulk_items_integration_test.rb +73 -0
  75. data/test/system/workarea/storefront/product_variant_list_system_test.rb +92 -0
  76. data/test/teaspoon_env.rb +6 -0
  77. data/test/test_helper.rb +10 -0
  78. data/workarea-variant_list.gemspec +21 -0
  79. metadata +142 -0
@@ -0,0 +1,40 @@
1
+ /*------------------------------------*\
2
+ #PRODUCT-DETAILS
3
+ \*------------------------------------*/
4
+
5
+ .product-details--variant-list {}
6
+
7
+ .product-details__variant-table {}
8
+
9
+ .product-details__variant-table-head {}
10
+
11
+ .product-details__variant-table-body {}
12
+
13
+ .product-details__variant-table-row {}
14
+
15
+ .product-details__variant-table-cell {}
16
+
17
+ .product-details__variant-table-cell--image {}
18
+
19
+ .product-details__variant-table-cell--name {
20
+ .product-details__variant-table-body & {
21
+ font-weight: bold;
22
+ }
23
+ }
24
+
25
+ .product-details__variant-table-cell--detail {
26
+ text-align: center;
27
+ }
28
+
29
+ .product-details__variant-table-cell--price {
30
+ text-align: right;
31
+
32
+ .product-details__variant-table-body & {
33
+ font-weight: bold;
34
+ }
35
+ }
36
+
37
+ .product-details__variant-table-cell--quantity {
38
+ text-align: right;
39
+ }
40
+
@@ -0,0 +1,30 @@
1
+ module Workarea
2
+ module Storefront
3
+ class CartBulkItemsController < ApplicationController
4
+ include CheckInventory
5
+
6
+ skip_before_action :verify_authenticity_token
7
+
8
+ def create
9
+ add_to_cart = AddMultipleCartItems.new(
10
+ current_order,
11
+ params[:items].map(&:to_unsafe_h).select { |p| p[:quantity].to_i.positive? }
12
+ )
13
+
14
+ if add_to_cart.perform!
15
+ check_inventory
16
+ Pricing.perform(current_order, current_shippings)
17
+
18
+ @cart = CartViewModel.new(current_order, view_model_options)
19
+ @items = add_to_cart.items.map do |cart_item|
20
+ OrderItemViewModel.wrap(cart_item.item, view_model_options)
21
+ end
22
+ else
23
+ flash[:error] =
24
+ t('workarea.storefront.flash_messages.cart_bulk_items_error')
25
+ redirect_to product_path(add_to_cart.items.first.product)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ module Workarea
2
+ module Storefront
3
+ class ProductTemplates::VariantListViewModel < ProductViewModel
4
+ delegate :all_options, to: :option_set
5
+
6
+ def variant_list
7
+ @variant_list ||= variants.map do |variant|
8
+ VariantViewModel.new(
9
+ variant,
10
+ options.merge(inventory: inventory)
11
+ )
12
+ end
13
+ end
14
+
15
+ def option_set
16
+ @option_set ||= ProductViewModel::OptionSet.new(self, options)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ module Workarea
2
+ module Storefront
3
+ class VariantViewModel < ApplicationViewModel
4
+ delegate :inventory_status, :images, :primary_image, :inventory,
5
+ :inventory_purchasable?, :pricing, to: :product
6
+
7
+ def product
8
+ @product ||=
9
+ ProductViewModel.wrap(model.product, options.merge(sku: sku))
10
+ end
11
+
12
+ def details
13
+ @details ||= Hash[
14
+ model.details.map { |k, v| [k.to_s.optionize, [v].flatten.join(', ')] }
15
+ ]
16
+ end
17
+
18
+ def price
19
+ @price ||= pricing.for_sku(sku)
20
+ end
21
+
22
+ def purchasable?
23
+ product.model.purchasable? && inventory_purchasable? && price.persisted?
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ .view
2
+ - if @items.blank?
3
+ = render_message 'error', data: { message_show_dismiss: 'false' } do
4
+ = t('workarea.storefront.flash_messages.cart_item_error')
5
+
6
+ - else
7
+ = render_message 'success', data: { message_show_dismiss: 'false' } do
8
+ = t('workarea.storefront.flash_messages.cart_bulk_items_added')
9
+
10
+ .product-list
11
+ - @items.each do |item|
12
+ .product-list__item{ data: { cart_count: current_order.quantity, analytics: add_to_cart_confirmation_analytics_data(item).to_json } }
13
+ .product-list__item-cell
14
+ .product-list__summary
15
+ %p.product-list__media= link_to image_tag(product_image_url(item.image, :small_thumb), alt: item.product_name, class: 'product-list__media-image'), product_url(item.product, sku: item.sku), class: 'product-list__media-link'
16
+ .product-list__info
17
+ %p.product-list__name= link_to item.product_name, product_path(item.product, sku: item.sku)
18
+ %p.product-list__id= item.sku_name
19
+ - if item.has_options?
20
+ .product-list__option-group
21
+ - item.details.each do |name, value|
22
+ %p.product-list__option #{name.titleize}: #{value}
23
+ - item.customizations.each do |name, value|
24
+ %p.product-list__customization #{name.titleize}: #{value}
25
+ = append_partials('storefront.cart_item_details', item: item, index: 0)
26
+ .product-list__item-cell
27
+ %table.table
28
+ %thead
29
+ %tr
30
+ %th.table__prices= t('workarea.storefront.orders.price')
31
+ %th.table__quantity= t('workarea.storefront.orders.quantity')
32
+ %th.table__prices= t('workarea.storefront.orders.total')
33
+ %tbody
34
+ %tr
35
+ %td.table__prices
36
+ = render 'workarea/storefront/carts/pricing', item: item, css_block: 'table'
37
+ %td.table__quantity
38
+ = form_tag cart_item_path(item), method: :patch, class: 'inline-form', data: { analytics: update_cart_item_analytics_data(item).to_json } do
39
+ .inline-form__cell
40
+ .value= number_field_tag :quantity, item.quantity, min: 1, required: true, class: 'text-box text-box--x-small', data: { form_submitting_control: '' }, title: t('workarea.storefront.orders.quantity'), id: dom_id(item, 'cart_item')
41
+ %p.inline-form__cell.hidden-if-js-enabled= button_tag t('workarea.storefront.carts.update'), value: 'change_quantity', class: 'button'
42
+ %td.table__prices
43
+ - item.total_adjustments.each do |adjustment|
44
+ %p.table__price
45
+ - if item.total_adjustments.many?
46
+ %span.table__price-label= adjustment.description.titleize
47
+
48
+ - if adjustment.discount?
49
+ %strong.table__price-discount= number_to_currency(adjustment.amount)
50
+ - else
51
+ %span= number_to_currency(adjustment.amount)
52
+
53
+ - if item.total_adjustments.many?
54
+ %p.table__price
55
+ %span.table__price-label= t('workarea.storefront.orders.item_total')
56
+ %span= number_to_currency(item.total_price)
57
+
58
+ .grid.grid--auto.grid--center
59
+ .grid__cell= link_to t('workarea.storefront.carts.continue_shopping'), root_path, class: 'button button--large', data: { dialog_close_button: true }
60
+ .grid__cell= link_to t('workarea.storefront.carts.view_cart'), cart_path, class: 'button button--large'
61
+ .grid__cell= link_to t('workarea.storefront.carts.checkout'), checkout_path, class: 'button button--large'
62
+
63
+ - if @cart.recommendations.any?
64
+ .hidden.hidden--for-small-only
65
+ %h2= t('workarea.storefront.recommendations.you_may_also_like')
66
+
67
+ .grid
68
+ - @cart.recommendations.each do |product|
69
+ .grid__cell.grid__cell--50.grid__cell--33-at-medium.grid__cell--16-at-wide
70
+ .product-summary.product-summary--small{ itemscope: true, itemtype: 'http://schema.org/Product' }
71
+ = render 'workarea/storefront/products/summary', product: product
@@ -0,0 +1,82 @@
1
+ .grid.grid--rev
2
+ .grid__cell.grid__cell--60-at-medium
3
+
4
+ .product-details__name
5
+ %h1{ itemprop: 'name' }= product.name
6
+
7
+ %p.product-details__id
8
+ %span{ itemprop: 'productID' }= product.id
9
+
10
+ .product-prices.product-prices--details{ itemprop: 'offers', itemscope: true, itemtype: 'http://schema.org/Offer' }
11
+ = render 'workarea/storefront/products/pricing', product: product
12
+
13
+ - if product.description.present?
14
+ .product-details__description
15
+ %p= truncated_product_description(product, t('workarea.storefront.products.read_more'))
16
+
17
+ = form_tag cart_bulk_items_path, method: 'post', class: 'product-details__add-to-cart-form', data: { dialog_form: { dialogOptions: { closeAll: true, initModules: true } }, analytics: add_to_cart_analytics_data(product).to_json } do
18
+ = hidden_field_tag :via, params[:via], id: dom_id(product, 'via')
19
+
20
+ %table.product-details__variant-table{ data: { sortable_table: '' } }
21
+ %thead.product-details__variant-table-head
22
+ %tr.product-details__variant-table-row
23
+ %th.product-details__variant-table-cell.product-details__variant-table-cell--image
24
+ %th.product-details__variant-table-cell.product-details__variant-table-cell--sku{ data: { sortable_table_header: '' } }= t('workarea.storefront.variant_list.products.sku')
25
+ - product.all_options.each do |option|
26
+ %th.product-details__variant-table-cell.product-details__variant-table-cell--detail{ data: { sortable_table_header: '' } }= option.titleize
27
+ %th.product-details__variant-table-cell.product-details__variant-table-cell--price{ data: { sortable_table_header: '' } }= t('workarea.storefront.variant_list.products.price')
28
+ %th.product-details__variant-table-cell.product-details__variant-table-cell--quantity= t('workarea.storefront.products.quantity')
29
+ %tbody.product-details__variant-table-body
30
+ - product.variant_list.each_with_index do |variant, index|
31
+ %tr.product-details__variant-table-row
32
+ %td.product-details__variant-table-cell.product-details__variant-table-cell--image
33
+ = image_tag product_image_url(variant.primary_image, :small_thumb), alt: t('workarea.storefront.products.image_alt_attribute', name: variant.name), itemprop: 'image'
34
+ %td.product-details__variant-table-cell.product-details__variant-table-cell--name
35
+ = variant.name
36
+
37
+ - product.all_options.each do |option|
38
+ %td.product-details__variant-table-cell.product-details__variant-table-cell--detail{ class: "product-details__variant-table-cell--detail-#{option}" }
39
+ - if variant.details[option].present?
40
+ = variant.details[option]
41
+ - else
42
+ \-
43
+
44
+ %td.product-details__variant-table-cell.product-details__variant-table-cell--price= number_to_currency(variant.price.sell)
45
+ %td.product-details__variant-table-cell.product-details__variant-table-cell--quantity
46
+ - if variant.purchasable?
47
+ = hidden_field_tag 'items[][sku]', variant.sku, id: nil
48
+
49
+ .property
50
+ .value
51
+ = number_field_tag "items[][quantity]", params[:items].try(:[], index).try(:[], :quantity) || 0, class: 'text-box text-box--x-small', min: 0, id: "quantity_#{dom_id(variant)}"
52
+ .value__note= variant.inventory_status
53
+ - else
54
+ %p= t('workarea.storefront.variant_list.products.variant_unavailable')
55
+
56
+ = append_partials('storefront.add_to_cart_form', product: product)
57
+
58
+ - if product.purchasable?
59
+ %p.product-details__add-to-cart-action= button_tag t('workarea.storefront.products.add_to_cart'), value: 'add_to_cart', class: 'button button--large'
60
+
61
+ - else
62
+ = hidden_field_tag :quantity, params[:quantity] || 1, id: "quantity#{dom_id(product)}"
63
+ %p.product-details__unavailable= t('workarea.storefront.products.unavailable')
64
+
65
+ = append_partials('storefront.product_details', product: product)
66
+
67
+ %p.product-details__full-details=link_to t('workarea.storefront.products.view_full_details'), product_path(product, color: params[:color]), class: 'text-button', itemprop: 'url'
68
+
69
+ .grid__cell.grid__cell--40-at-medium
70
+
71
+ .product-details__primary-image
72
+ = link_to(product_image_url(product.primary_image, :zoom), target: '_blank', rel: 'noopener', class: 'product-details__primary-image-link', data: { dialog_button: '' }) do
73
+ = image_tag product_image_url(product.primary_image, :detail), alt: t('workarea.storefront.products.image_alt_attribute', name: product.name), itemprop: 'image', class: 'product-details__primary-image-link-image'
74
+
75
+ - if product.images.length > 1
76
+ .product-details__alt-images
77
+ .grid.grid--auto
78
+ - product.images.each_with_index do |image, index|
79
+ .grid__cell
80
+ .product-details__alt-image
81
+ - button_class = index == 0 ? 'product-details__alt-image-link product-details__alt-image-link--selected' : 'product-details__alt-image-link'
82
+ = link_to(image_tag(product_image_url(image, :small_thumb), alt: t('workarea.storefront.products.zoom')), product_image_url(image, :zoom), class: button_class, target: '_blank', rel: 'noopener', data: { alternate_image_button: { src: product_image_url(image, :detail) }.to_json })
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
6
+ ENGINE_PATH = File.expand_path('../../lib/workarea/variant_list/engine', __FILE__)
7
+ APP_PATH = File.expand_path('../../test/dummy/config/application', __FILE__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
11
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
12
+
13
+ require "action_controller/railtie"
14
+ require "action_view/railtie"
15
+ require "action_mailer/railtie"
16
+ require "rails/test_unit/railtie"
17
+ require "sprockets/railtie"
18
+ require 'teaspoon-mocha'
19
+
20
+ require 'rails/engine/commands'
@@ -0,0 +1,9 @@
1
+ Workarea.append_stylesheets(
2
+ 'storefront.components',
3
+ 'workarea/storefront/variant_list/components/product_details'
4
+ )
5
+
6
+ Workarea.append_javascripts(
7
+ 'storefront.modules',
8
+ 'workarea/storefront/variant_list/modules/sortable_tables'
9
+ )
@@ -0,0 +1,4 @@
1
+ Workarea.configure do |config|
2
+ config.product_templates << :variant_list
3
+ config.product_quickview_templates.try(:push, :variant_list)
4
+ end
@@ -0,0 +1,11 @@
1
+ en:
2
+ workarea:
3
+ storefront:
4
+ flash_messages:
5
+ cart_bulk_items_added: These items have been added to your cart.
6
+ cart_bulk_items_error: Some items could not be added to your cart.
7
+ variant_list:
8
+ products:
9
+ variant_unavailable: This SKU is currently unavailable for purchase.
10
+ sku: SKU
11
+ price: Price
@@ -0,0 +1,7 @@
1
+ Workarea::Storefront::Engine.routes.draw do
2
+ scope '(:locale)', constraints: Workarea::I18n.routes_constraint do
3
+ resource :cart, only: [] do
4
+ resource :bulk_items, only: :create, controller: :cart_bulk_items
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require 'workarea'
2
+ require 'workarea/storefront'
3
+ require 'workarea/admin'
4
+
5
+ require 'workarea/variant_list/engine'
6
+ require 'workarea/variant_list/version'
7
+
8
+ module Workarea
9
+ module VariantList
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Workarea
2
+ module VariantList
3
+ class Engine < ::Rails::Engine
4
+ include Workarea::Plugin
5
+ isolate_namespace Workarea::VariantList
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Workarea
2
+ module VariantList
3
+ VERSION = '1.0.2'.freeze
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative 'config/application'
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,4 @@
1
+
2
+ //= link_tree ../images
3
+ //= link_directory ../javascripts .js
4
+ //= link_directory ../stylesheets .css
File without changes
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,3 @@
1
+ class ApplicationController < ActionController::Base
2
+ protect_from_forgery with: :exception
3
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: 'from@example.com'
3
+ layout 'mailer'
4
+ end
File without changes
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= csrf_meta_tags %>
6
+
7
+ <%= stylesheet_link_tag 'application', media: 'all' %>
8
+ <%= javascript_include_tag 'application' %>
9
+ </head>
10
+
11
+ <body>
12
+ <%= yield %>
13
+ </body>
14
+ </html>