spree_group_buy 0.0.1

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