spree_group_buy 0.0.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +24 -0
  5. data/.travis.yml +49 -0
  6. data/Appraisals +20 -0
  7. data/Gemfile +11 -0
  8. data/LICENSE +26 -0
  9. data/README.md +51 -0
  10. data/Rakefile +21 -0
  11. data/app/.gitkeep +0 -0
  12. data/app/controllers/.gitkeep +0 -0
  13. data/app/controllers/spree/admin/group_buys_controller.rb +15 -0
  14. data/app/controllers/spree/admin/orders_controller_decorator.rb +17 -0
  15. data/app/controllers/spree/api/v2/storefront/cart_controller_decorator.rb +9 -0
  16. data/app/controllers/spree/products_controller_decorator.rb +20 -0
  17. data/app/mailers/spree/order_mailer_decorator.rb +10 -0
  18. data/app/models/.gitkeep +0 -0
  19. data/app/models/spree/group_buy.rb +72 -0
  20. data/app/models/spree/line_item_decorator.rb +18 -0
  21. data/app/models/spree/order_decorator.rb +68 -0
  22. data/app/models/spree/product_decorator.rb +8 -0
  23. data/app/models/spree/stock/packer_decorator.rb +27 -0
  24. data/app/models/spree/stock/prioritizer_decorator.rb +18 -0
  25. data/app/services/.gitkeep +0 -0
  26. data/app/services/spree/cart/group_buy_add_item.rb +66 -0
  27. data/app/views/.gitkeep +0 -0
  28. data/app/views/spree/admin/group_buys/_form.html.erb +32 -0
  29. data/app/views/spree/admin/group_buys/edit.html.erb +13 -0
  30. data/app/views/spree/admin/group_buys/index.html.erb +49 -0
  31. data/app/views/spree/admin/group_buys/new.html.erb +15 -0
  32. data/app/views/spree/admin/orders/index.html.erb +271 -0
  33. data/app/views/spree/admin/shared/_main_menu.html.erb +49 -0
  34. data/app/views/spree/admin/shared/_product_tabs.html.erb +56 -0
  35. data/bin/rails +8 -0
  36. data/config/locales/en.yml +5 -0
  37. data/config/routes.rb +8 -0
  38. data/db/migrate/20200824192531_create_spree_group_buys.rb +15 -0
  39. data/db/migrate/20200826140754_add_group_buy_to_line_items.rb +5 -0
  40. data/db/migrate/20201005210530_add_group_buy_to_orders.rb +5 -0
  41. data/gemfiles/spree_3_7.gemfile +9 -0
  42. data/gemfiles/spree_4_0.gemfile +8 -0
  43. data/gemfiles/spree_4_1.gemfile +9 -0
  44. data/gemfiles/spree_master.gemfile +8 -0
  45. data/lib/generators/spree_group_buy/install/install_generator.rb +26 -0
  46. data/lib/generators/templates/vendor/assets/javascripts/spree/frontend/views/spree/products/cart_form.js +388 -0
  47. data/lib/generators/templates/vendor/assets/javascripts/spree/frontend/views/spree/shared/product_added_modal.js +28 -0
  48. data/lib/spree_group_buy.rb +4 -0
  49. data/lib/spree_group_buy/engine.rb +20 -0
  50. data/lib/spree_group_buy/factories.rb +6 -0
  51. data/lib/spree_group_buy/version.rb +17 -0
  52. data/spree_group_buy.gemspec +32 -0
  53. metadata +197 -0
@@ -0,0 +1,49 @@
1
+ <nav>
2
+ <% if can? :admin, Spree::Order %>
3
+ <ul class="nav nav-sidebar border-bottom">
4
+ <%= tab *Spree::BackendConfiguration::ORDER_TABS, icon: 'shopping-cart' %>
5
+ </ul>
6
+ <% end %>
7
+
8
+ <% if can? :admin, Spree::Order %>
9
+ <ul class="nav nav-sidebar border-bottom">
10
+ <%= tab :group_buy_orders, url: '/admin/orders?q[group_buy_eq]=1', icon: 'shopping-cart' %>
11
+ </ul>
12
+ <% end %>
13
+
14
+ <% if can?(:admin, Spree::ReturnAuthorization) || can?(:admin, Spree::CustomerReturn) %>
15
+ <ul class="nav nav-sidebar border-bottom">
16
+ <%= main_menu_tree Spree.t(:returns), icon: "transfer", sub_menu: "returns", url: "#sidebar-returns" %>
17
+ </ul>
18
+ <% end %>
19
+
20
+ <% if can? :admin, Spree::Product %>
21
+ <ul class="nav nav-sidebar border-bottom">
22
+ <%= main_menu_tree Spree.t(:products), icon: "th-large", sub_menu: "product", url: "#sidebar-product" %>
23
+ </ul>
24
+ <% end %>
25
+
26
+ <% if can? :admin, Spree::Admin::ReportsController %>
27
+ <ul class="nav nav-sidebar border-bottom">
28
+ <%= tab *Spree::BackendConfiguration::REPORT_TABS, icon: 'file' %>
29
+ </ul>
30
+ <% end %>
31
+
32
+ <% if can? :admin, Spree::Promotion %>
33
+ <ul class="nav nav-sidebar border-bottom">
34
+ <%= main_menu_tree Spree.t(:promotions), icon: "gift", sub_menu: "promotion", url: "#sidebar-promotions" %>
35
+ </ul>
36
+ <% end %>
37
+
38
+ <% if Spree.user_class && can?(:admin, Spree.user_class) %>
39
+ <ul class="nav nav-sidebar border-bottom">
40
+ <%= tab *Spree::BackendConfiguration::USER_TABS, url: spree.admin_users_path, icon: 'user' %>
41
+ </ul>
42
+ <% end %>
43
+
44
+ <% if can? :admin, current_store %>
45
+ <ul class="nav nav-sidebar border-bottom">
46
+ <%= main_menu_tree Spree.t(:configurations), icon: "wrench", sub_menu: "configuration", url: "#sidebar-configuration" %>
47
+ </ul>
48
+ <% end %>
49
+ </nav>
@@ -0,0 +1,56 @@
1
+ <% content_for :page_title do %>
2
+ <%= link_to Spree.t(:products), admin_products_path %> /
3
+ <%= @product.name %>
4
+ <% end %>
5
+
6
+ <% content_for :sidebar do %>
7
+ <ul class="nav flex-column nav-pills" data-hook="admin_product_tabs">
8
+ <%= content_tag :li do %>
9
+ <%= link_to_with_icon 'edit',
10
+ Spree.t(:details),
11
+ edit_admin_product_url(@product),
12
+ class: "nav-link #{'active' if current == :details}" %>
13
+
14
+ <% end if can?(:admin, Spree::Product) %>
15
+
16
+ <%= content_tag :li do %>
17
+ <%= link_to_with_icon 'picture',
18
+ Spree.t(:images),
19
+ spree.admin_product_images_url(@product),
20
+ class: "nav-link #{'active' if current == :images}" %>
21
+
22
+ <% end if can?(:admin, Spree::Image) && !@product.deleted? %>
23
+
24
+ <%= content_tag :li do %>
25
+ <%= link_to_with_icon 'th-large',
26
+ Spree.t(:variants),
27
+ spree.admin_product_variants_url(@product),
28
+ class: "nav-link #{'active' if current == :variants}" %>
29
+
30
+ <% end if can?(:admin, Spree::Variant) && !@product.deleted? %>
31
+
32
+ <%= content_tag :li do %>
33
+ <%= link_to_with_icon 'list-alt',
34
+ Spree.t(:properties),
35
+ spree.admin_product_product_properties_url(@product),
36
+ class: "nav-link #{'active' if current == :properties}" %>
37
+
38
+ <% end if can?(:admin, Spree::ProductProperty) && !@product.deleted? %>
39
+
40
+ <%= content_tag :li do %>
41
+ <%= link_to_with_icon 'home',
42
+ Spree.t(:stock),
43
+ stock_admin_product_url(@product),
44
+ class: "nav-link #{'active' if current == :stock}" %>
45
+
46
+ <% end if can?(:admin, Spree::StockItem) && !@product.deleted? %>
47
+
48
+ <%= content_tag :li do %>
49
+ <%= link_to_with_icon 'money',
50
+ Spree.t(:group_buys),
51
+ admin_product_group_buys_url(@product),
52
+ class: "nav-link #{'active' if current == :group_buys}" %>
53
+
54
+ <% end if can?(:admin, Spree::GroupBuy) && !@product.deleted? %>
55
+ </ul>
56
+ <% end %>
@@ -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_group_buy/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'
@@ -0,0 +1,5 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ hello: "Hello world"
@@ -0,0 +1,8 @@
1
+ Spree::Core::Engine.add_routes do
2
+ # Add your extension routes here
3
+ namespace :admin do
4
+ resources :products do
5
+ resources :group_buys
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ class CreateSpreeGroupBuys < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :spree_group_buys do |t|
4
+ t.references :product
5
+ t.integer :quantity, null: false
6
+ t.decimal :price, precision: 8, scale: 2
7
+ t.string :currency
8
+ t.datetime :expires_at
9
+ t.integer :engaged_count, default: 0, null: false
10
+ t.string :state
11
+ t.timestamps
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ class AddGroupBuyToLineItems < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_reference :spree_line_items, :group_buy
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddGroupBuyToOrders < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :spree_orders, :group_buy, :boolean, default: false, after: 'considered_risky'
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails-controller-testing"
6
+ gem "spree", "~> 3.7.0"
7
+ gem "sass-rails"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails-controller-testing"
6
+ gem "spree", "~> 4.0.0"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+
2
+ # This file was generated by Appraisal
3
+
4
+ source "https://rubygems.org"
5
+
6
+ gem "rails-controller-testing"
7
+ gem "spree", "~> 4.1.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails-controller-testing"
6
+ gem "spree", github: "spree/spree", branch: "master"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,26 @@
1
+ module SpreeGroupBuy
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ class_option :auto_run_migrations, type: :boolean, default: false
5
+
6
+ source_root File.expand_path('../../../templates', __FILE__)
7
+
8
+ def copy_javascripts
9
+ directory 'vendor'
10
+ end
11
+
12
+ def add_migrations
13
+ run 'bundle exec rake railties:install:migrations FROM=spree_group_buy'
14
+ end
15
+
16
+ def run_migrations
17
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
18
+ if run_migrations
19
+ run 'bundle exec rails db:migrate'
20
+ else
21
+ puts 'Skipping rails db:migrate, don\'t forget to run it!'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,388 @@
1
+ //= require spree/api/storefront/cart
2
+
3
+ var ADD_TO_CART_FORM_SELECTOR = '.add-to-cart-form'
4
+ var VARIANT_ID_SELECTOR = '[name="variant_id"]'
5
+ var OPTION_VALUE_SELECTOR = '.product-variants-variant-values-radio'
6
+ var ADD_TO_CART_SELECTOR = '.add-to-cart-button'
7
+ var ADD_TO_CART_DEAL_SELECTOR = '.add-to-cart-deal-button'
8
+ var GROUP_BUY_SELECTOR = '.group_buy_options_radio'
9
+
10
+ var AVAILABILITY_TEMPLATES = {
11
+ notAvailableInCurrency: '.availability-template-not-available-in-currency',
12
+ inStock: '.availability-template-in-stock',
13
+ backorderable: '.availability-template-backorderable',
14
+ outOfStock: '.availability-template-out-of-stock'
15
+ }
16
+
17
+
18
+ function CartForm($, $cartForm) {
19
+ this.constructor = function() {
20
+ this.initialize()
21
+ this.bindEventHandlers()
22
+ }
23
+
24
+ this.initialize = function() {
25
+ this.selectedOptionValueIds = []
26
+ this.variants = JSON.parse($cartForm.attr('data-variants'))
27
+ this.withOptionValues = Boolean($cartForm.find(OPTION_VALUE_SELECTOR).length)
28
+
29
+ this.$addToCart = $cartForm.find(ADD_TO_CART_SELECTOR)
30
+ this.$addToCartDeal = $cartForm.find(ADD_TO_CART_DEAL_SELECTOR)
31
+ this.$price = $cartForm.find('.price.selling')
32
+ this.$variantIdInput = $cartForm.find(VARIANT_ID_SELECTOR)
33
+
34
+ this.initializeForm()
35
+ }
36
+
37
+ this.initializeForm = function() {
38
+ if (this.withOptionValues) {
39
+ var $optionValue = this.firstCheckedOptionValue()
40
+ this.applyCheckedOptionValue($optionValue, true)
41
+ } else {
42
+ this.updateAddToCart()
43
+ this.triggerVariantImages()
44
+ }
45
+ }
46
+
47
+ this.bindEventHandlers = function() {
48
+ $cartForm.on('click', OPTION_VALUE_SELECTOR, this.handleOptionValueClick)
49
+ // $cartForm.on('click', GROUP_BUY_SELECTOR, this.handleGroupBuyClick)
50
+ }
51
+
52
+ this.handleOptionValueClick = function(event) {
53
+ this.applyCheckedOptionValue($(event.currentTarget))
54
+ }.bind(this)
55
+
56
+ this.handleGroupBuyClick = function(event) {
57
+ var checkboxes = $cartForm.find(GROUP_BUY_SELECTOR)
58
+ var current_val = parseInt($(event.currentTarget).val())
59
+ $cartForm.find(GROUP_BUY_SELECTOR)
60
+ .each(function(_index, ov) {
61
+ var $ov = $(ov)
62
+ var id = parseInt($ov.val())
63
+ if(id != current_val) {
64
+ $ov.prop('checked', false)
65
+ }
66
+
67
+ })
68
+ }.bind(this)
69
+
70
+ this.applyCheckedOptionValue = function($optionValue, initialUpdate) {
71
+ this.saveCheckedOptionValue($optionValue)
72
+ this.showAvailableVariants()
73
+ this.updateAddToCart()
74
+ // we don't want to remove availability status on initial page load
75
+ if (!initialUpdate) this.updateVariantAvailability()
76
+ this.updateVariantPrice()
77
+ this.updateVariantId()
78
+
79
+ if (this.shouldTriggerVariantImage($optionValue)) {
80
+ this.triggerVariantImages()
81
+ }
82
+ }
83
+
84
+ this.saveCheckedOptionValue = function($optionValue) {
85
+ var optionTypeIndex = $optionValue.data('option-type-index')
86
+
87
+ this.selectedOptionValueIds.splice(
88
+ optionTypeIndex,
89
+ this.selectedOptionValueIds.length,
90
+ parseInt($optionValue.val())
91
+ )
92
+ }
93
+
94
+ this.showAvailableVariants = function() {
95
+ var availableOptionValueIds = this.availableOptionValueIds()
96
+ var selectedOptionValueIdsCount = this.selectedOptionValueIds.length
97
+
98
+ this.optionTypes().each(function(index, optionType) {
99
+ if (index < selectedOptionValueIdsCount) return
100
+
101
+ $(optionType)
102
+ .find(OPTION_VALUE_SELECTOR)
103
+ .each(function(_index, ov) {
104
+ var $ov = $(ov)
105
+ var id = parseInt($ov.val())
106
+
107
+ $ov.prop('checked', false)
108
+ $ov.prop('disabled', !availableOptionValueIds.includes(id))
109
+ })
110
+ })
111
+ }
112
+
113
+ this.optionTypes = function() {
114
+ return $cartForm.find('.product-variants-variant')
115
+ }
116
+
117
+ this.availableOptionValueIds = function() {
118
+ var selectedOptionValueIds = this.selectedOptionValueIds
119
+
120
+ return this.variants.reduce(function(acc, variant) {
121
+ var optionValues = variant.option_values.map(function(ov) {
122
+ return ov.id
123
+ })
124
+
125
+ var isPossibleVariantFound = selectedOptionValueIds.every(function(ov) {
126
+ return optionValues.includes(ov)
127
+ })
128
+
129
+ if (isPossibleVariantFound) {
130
+ return acc.concat(optionValues)
131
+ }
132
+
133
+ return acc
134
+ }, [])
135
+ }
136
+
137
+ this.firstCheckedOptionValue = function() {
138
+ return $cartForm.find(OPTION_VALUE_SELECTOR + '[data-option-type-index=0]' + ':checked')
139
+ }
140
+
141
+ this.shouldTriggerVariantImage = function($optionValue) {
142
+ return $optionValue.data('is-color') || !this.firstCheckedOptionValue().data('is-color')
143
+ }
144
+
145
+ this.triggerVariantImages = function() {
146
+ var checkedVariantId
147
+ var variant = this.selectedVariant()
148
+
149
+ if (variant) {
150
+ checkedVariantId = variant.id
151
+ } else {
152
+ checkedVariantId = this.firstCheckedOptionValue().data('variant-id')
153
+ }
154
+
155
+ // Wait for listeners to attach.
156
+ setTimeout(function() {
157
+ $cartForm.trigger({
158
+ type: 'variant_id_change',
159
+ triggerId: $cartForm.attr('data-variant-change-trigger-identifier'),
160
+ variantId: checkedVariantId + ''
161
+ })
162
+ })
163
+ }
164
+
165
+ this.selectedVariant = function() {
166
+ var self = this
167
+
168
+ if (!this.withOptionValues) {
169
+ return this.variants.find(function(variant) {
170
+ return variant.id === parseInt(self.$variantIdInput.val())
171
+ })
172
+ }
173
+
174
+ if (this.variants.length === 1 && this.variants[0].is_master) {
175
+ return this.variants[0]
176
+ }
177
+
178
+ return this.variants.find(function(variant) {
179
+ var optionValueIds = variant.option_values.map(function(ov) {
180
+ return ov.id
181
+ })
182
+
183
+ return self.areArraysEqual(optionValueIds, self.selectedOptionValueIds)
184
+ })
185
+ }
186
+
187
+ this.areArraysEqual = function(array1, array2) {
188
+ return this.sortArray(array1).join(',') === this.sortArray(array2).join(',')
189
+ }
190
+
191
+ this.sortArray = function(array) {
192
+ return array.concat().sort(function(a, b) {
193
+ if (a < b) return -1
194
+ if (a > b) return 1
195
+
196
+ return 0
197
+ })
198
+ }
199
+
200
+ this.updateAddToCart = function() {
201
+ var variant = this.selectedVariant()
202
+
203
+ this.$addToCart.prop('disabled', variant ? !variant.purchasable : true)
204
+ this.$addToCartDeal.prop('disabled', variant ? !variant.purchasable : true)
205
+ }
206
+
207
+ this.availabilityMessage = function(variant) {
208
+ if (!variant.is_product_available_in_currency) {
209
+ return $(AVAILABILITY_TEMPLATES.notAvailableInCurrency).html()
210
+ }
211
+
212
+ if (variant.in_stock) {
213
+ return $(AVAILABILITY_TEMPLATES.inStock).html()
214
+ }
215
+
216
+ if (variant.backorderable) {
217
+ return $(AVAILABILITY_TEMPLATES.backorderable).html()
218
+ }
219
+
220
+ return $(AVAILABILITY_TEMPLATES.outOfStock).html()
221
+ }
222
+
223
+ this.updateVariantAvailability = function() {
224
+ var variant = this.selectedVariant()
225
+
226
+ if (!variant) {
227
+ return $cartForm
228
+ .find('.add-to-cart-form-general-availability .add-to-cart-form-general-availability-value')
229
+ .html('')
230
+ }
231
+
232
+ return $cartForm
233
+ .find('.add-to-cart-form-general-availability .add-to-cart-form-general-availability-value')
234
+ .html(this.availabilityMessage(variant))
235
+ }
236
+
237
+ this.updateVariantPrice = function() {
238
+ var variant = this.selectedVariant()
239
+
240
+ if (!variant) return
241
+
242
+ this.$price.html(variant.display_price)
243
+ }
244
+
245
+ this.updateVariantId = function() {
246
+ var variant = this.selectedVariant()
247
+ var variantId = (variant && variant.id) || ''
248
+
249
+ this.$variantIdInput.val(variantId)
250
+ }
251
+
252
+ this.constructor()
253
+ }
254
+
255
+ function countdown() {
256
+ var countdown = document.getElementById("countdown");
257
+
258
+ if(!countdown) { return }
259
+
260
+ var expireDate = $('#expires_at').text();
261
+
262
+ // Set the date we're counting down to
263
+ var countDownDate = new Date(expireDate).getTime();
264
+
265
+ // Update the count down every 1 second
266
+ var x = setInterval(function() {
267
+
268
+ // Get today's date and time
269
+ var now = new Date().getTime();
270
+
271
+ // Find the distance between now and the count down date
272
+ var distance = countDownDate - now;
273
+
274
+ // Time calculations for days, hours, minutes and seconds
275
+ var days = Math.floor(distance / (1000 * 60 * 60 * 24));
276
+ var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
277
+ var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
278
+ var seconds = Math.floor((distance % (1000 * 60)) / 1000);
279
+
280
+ // Display the result in the element with id="demo"
281
+ countdown.innerHTML = days + "d " + hours + "h "
282
+ + minutes + "m " + seconds + "s ";
283
+
284
+ // If the count down is finished, write some text
285
+ if (distance < 0) {
286
+ clearInterval(x);
287
+ countdown.innerHTML = "EXPIRED";
288
+ }
289
+ }, 1000);
290
+ }
291
+
292
+ Spree.ready(function($) {
293
+ Spree.variantById = function($cartForm, variantId) {
294
+ var cartFormVariants = JSON.parse($cartForm.attr('data-variants'))
295
+ return (
296
+ cartFormVariants.find(function(variant) {
297
+ return variant.id.toString() === variantId
298
+ }) || null
299
+ )
300
+ }
301
+
302
+ countdown();
303
+
304
+ $('#add-to-cart-button').on('click', function(event) {
305
+ var $cartForm = $(ADD_TO_CART_FORM_SELECTOR);
306
+ var checkboxes = $cartForm.find(GROUP_BUY_SELECTOR);
307
+ if(checkboxes.length > 0) {
308
+ $(checkboxes[0]).prop('checked', false);
309
+ }
310
+ $(ADD_TO_CART_SELECTOR).prop('disabled', true);
311
+ $cartForm.submit();
312
+ })
313
+
314
+ $('#add-to-cart-deal-button').on('click', function(event) {
315
+ var $cartForm = $(ADD_TO_CART_FORM_SELECTOR);
316
+ var checkboxes = $cartForm.find(GROUP_BUY_SELECTOR);
317
+ if(checkboxes.length > 0) {
318
+ $(checkboxes[0]).prop('checked', true);
319
+ }
320
+ $(ADD_TO_CART_DEAL_SELECTOR).prop('disabled', true);
321
+ $cartForm.submit();
322
+ })
323
+
324
+ $('#product-details').on('submit', ADD_TO_CART_FORM_SELECTOR, function(event) {
325
+ var variantId
326
+ var quantity
327
+ var group_buy_id
328
+ var group_buy_price
329
+ var group_buy_display_price
330
+ var $cartForm = $(event.currentTarget)
331
+ var $addToCart = $cartForm.find(ADD_TO_CART_SELECTOR)
332
+ var $addToCartDeal = $cartForm.find(ADD_TO_CART_DEAL_SELECTOR)
333
+ var $groupBuyChecked = $cartForm.find(GROUP_BUY_SELECTOR+':checked')
334
+
335
+ event.preventDefault()
336
+ // $addToCart.prop('disabled', true)
337
+ variantId = $cartForm.find(VARIANT_ID_SELECTOR).val()
338
+ quantity = parseInt($cartForm.find('[name="quantity"]').val())
339
+
340
+ if($groupBuyChecked.length) {
341
+ group_buy_id = $groupBuyChecked.val()
342
+ group_buy_price = $groupBuyChecked.data('price')
343
+ group_buy_display_price = $groupBuyChecked.data('display-price')
344
+ } else {
345
+ group_buy_id = group_buy_price = group_buy_display_price = undefined
346
+ }
347
+
348
+ Spree.ensureCart(function() {
349
+ SpreeAPI.Storefront.addToCart(
350
+ variantId,
351
+ quantity,
352
+ {groupBuyId: group_buy_id, groupBuyPrice: group_buy_price}, // options hash - you can pass additional parameters here, your backend
353
+ // needs to be aware of those, see API docs:
354
+ // https://github.com/spree/spree/blob/master/api/docs/v2/storefront/index.yaml#L42
355
+ function(response) {
356
+ $addToCart.prop('disabled', false)
357
+ $addToCartDeal.prop('disabled', false)
358
+ Spree.fetchCart()
359
+ Spree.showProductAddedModal(JSON.parse(
360
+ $cartForm.attr('data-product-summary')
361
+ ), Spree.variantById($cartForm, variantId), group_buy_display_price)
362
+ $cartForm.trigger({
363
+ type: 'product_add_to_cart',
364
+ variant: Spree.variantById($cartForm, variantId),
365
+ quantity_increment: quantity,
366
+ cart: response.attributes
367
+ })
368
+ },
369
+ function(error) {
370
+ if (typeof error === 'string' && error !== '') {
371
+ document.querySelector('#no-product-available .no-product-available-text').innerText = error
372
+ }
373
+ document.getElementById('overlay').classList.add('shown')
374
+ document.getElementById('no-product-available').classList.add('shown')
375
+ window.scrollTo(0, 0)
376
+ $addToCart.prop('disabled', false)
377
+ $addToCartDeal.prop('disabled', false)
378
+ } // failure callback for 422 and 50x errors
379
+ )
380
+ })
381
+ })
382
+
383
+ $(ADD_TO_CART_FORM_SELECTOR).each(function(_cartFormIndex, cartFormElement) {
384
+ var $cartForm = $(cartFormElement)
385
+
386
+ CartForm($, $cartForm)
387
+ })
388
+ })