stall 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/para/stall/inputs/variant-select.coffee +4 -3
- data/app/assets/javascripts/stall.coffee +3 -1
- data/app/assets/javascripts/stall/add-product-to-wish-list-button.coffee +99 -0
- data/app/assets/javascripts/stall/add-to-wish-list-button.coffee +17 -0
- data/app/assets/javascripts/stall/product-list-form.coffee +31 -0
- data/app/controllers/stall/cart_line_items_controller.rb +21 -0
- data/app/controllers/stall/curated_product_lists_controller.rb +6 -0
- data/app/controllers/stall/line_items_controller.rb +14 -7
- data/app/controllers/stall/products_breadcrumbs.rb +8 -1
- data/app/controllers/stall/products_controller.rb +12 -0
- data/app/controllers/stall/wish_list_line_items_controller.rb +52 -0
- data/app/controllers/stall/wish_lists_controller.rb +7 -0
- data/app/helpers/stall/add_to_cart_helper.rb +3 -2
- data/app/helpers/stall/add_to_wish_list_helper.rb +30 -0
- data/app/helpers/stall/products_helper.rb +13 -0
- data/app/models/stall/models/cart.rb +0 -6
- data/app/models/stall/models/customer.rb +9 -0
- data/app/models/stall/models/line_item.rb +13 -0
- data/app/models/stall/models/product.rb +1 -1
- data/app/models/stall/models/product_list.rb +4 -0
- data/app/models/stall/models/variant.rb +4 -0
- data/app/models/stall/models/wish_list.rb +18 -0
- data/app/models/wish_list.rb +3 -0
- data/app/services/stall/add_to_cart_service.rb +4 -47
- data/app/services/stall/add_to_product_list_service.rb +63 -0
- data/app/services/stall/add_to_wish_list_service.rb +17 -0
- data/app/services/stall/available_stocks_service.rb +1 -1
- data/app/services/stall/shipping_fee_calculator_service.rb +22 -9
- data/app/views/admin/products/_form.html.haml +5 -0
- data/app/views/checkout/steps/_informations.html.haml +1 -1
- data/app/views/stall/addresses/_fields.html.haml +4 -4
- data/app/views/stall/{line_items → cart_line_items}/_add_error.html.haml +0 -0
- data/app/views/stall/{line_items → cart_line_items}/_added.html.haml +2 -2
- data/app/views/stall/{line_items → cart_line_items}/_form.html.haml +1 -1
- data/app/views/stall/carts/_widget.html.haml +1 -1
- data/app/views/stall/carts/show.html.haml +4 -4
- data/app/views/stall/curated_product_lists/show.html.haml +1 -1
- data/app/views/stall/customers/_fields.html.haml +1 -1
- data/app/views/stall/customers/_sign_in.html.haml +2 -2
- data/app/views/stall/products/_list.html.haml +2 -0
- data/app/views/stall/products/_product.html.haml +2 -1
- data/app/views/stall/wish_list_line_items/_add_error.html.haml +17 -0
- data/app/views/stall/wish_list_line_items/_added.html.haml +19 -0
- data/app/views/stall/wish_list_line_items/_button.html.haml +3 -0
- data/app/views/stall/wish_list_line_items/_form.html.haml +12 -0
- data/app/views/stall/wish_lists/show.html.haml +21 -0
- data/config/locales/stall.fr.yml +26 -0
- data/db/migrate/20170425085606_add_weight_to_stall_products_and_variants.rb +6 -0
- data/db/migrate/20170426163450_add_vat_rate_to_stall_products.rb +5 -0
- data/db/migrate/20170522062334_change_variants_weight_default_to_nil.rb +11 -0
- data/lib/generators/stall/install/templates/initializer.rb +18 -0
- data/lib/stall.rb +1 -0
- data/lib/stall/addressable.rb +35 -4
- data/lib/stall/addresses/copy_source_to_target.rb +14 -10
- data/lib/stall/addresses/prefill_target_from_source.rb +10 -6
- data/lib/stall/config.rb +4 -0
- data/lib/stall/engine.rb +1 -0
- data/lib/stall/routes.rb +42 -19
- data/lib/stall/sellable.rb +1 -1
- data/lib/stall/shipping/calculator.rb +1 -1
- data/lib/stall/shipping/country_weight_table_calculator.rb +3 -2
- data/lib/stall/version.rb +1 -1
- data/lib/stall/wish_list_helper.rb +93 -0
- metadata +26 -6
- data/app/assets/javascripts/stall/cart-form.coffee +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac018d181d2997364eeca462e80758d50ab4c9e2
|
4
|
+
data.tar.gz: 4e3396a2466fab227f83d9d201a237e22147acc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c612280b92d9cbff047f8e7e0adf1f8dcc8abea245f7cd4cbf37170a68261c0d732f53266c11ba666fbf103c3b3998f267ce970fbb553b40880f88a2e52ee21e
|
7
|
+
data.tar.gz: b35431b6aa8856a8b3dc21686dff0346d0b724ec443176f044d022e0495563584a7e6d8ff537b227be52f91aed929b9e23bbacfa85ff7fb1c827024d7749cb51
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class VariantSelectInput extends Vertebra.View
|
1
|
+
class Stall.VariantSelectInput extends Vertebra.View
|
2
2
|
events:
|
3
3
|
'change [data-variant-select-property]': 'onInputChanged'
|
4
4
|
|
@@ -57,6 +57,7 @@ class VariantSelectInput extends Vertebra.View
|
|
57
57
|
price = if variant then variant.price else @originalPrice
|
58
58
|
@$priceTarget.html(price)
|
59
59
|
|
60
|
-
|
60
|
+
|
61
|
+
Stall.onDomReady ->
|
61
62
|
$('[data-variant-select-input]').each (i, el) ->
|
62
|
-
new VariantSelectInput(el: el)
|
63
|
+
new Stall.VariantSelectInput(el: el)
|
@@ -4,7 +4,9 @@
|
|
4
4
|
#= require_self
|
5
5
|
#
|
6
6
|
#= require stall/add-to-cart-form
|
7
|
-
#= require stall/
|
7
|
+
#= require stall/add-to-wish-list-button
|
8
|
+
#= require stall/add-product-to-wish-list-button
|
9
|
+
#= require stall/product-list-form
|
8
10
|
#= require stall/addresses-fields
|
9
11
|
#= require stall/remote-sign-in-form
|
10
12
|
#= require stall/products-filters
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class Stall.AddProductToWishListButton extends Vertebra.View
|
2
|
+
events:
|
3
|
+
'click': 'onButtonClicked'
|
4
|
+
|
5
|
+
onButtonClicked: ->
|
6
|
+
if @$el.is('[data-included]') then @remove() else @add()
|
7
|
+
|
8
|
+
add: ->
|
9
|
+
@setLoading(true)
|
10
|
+
|
11
|
+
data =
|
12
|
+
product_id: @$el.data('product-id')
|
13
|
+
|
14
|
+
$.post(@$el.data('url'), data).then(@onResponse)
|
15
|
+
|
16
|
+
remove: ->
|
17
|
+
@setLoading(true)
|
18
|
+
|
19
|
+
data =
|
20
|
+
_method: 'delete'
|
21
|
+
|
22
|
+
$.post(@$el.data('url'), data).then(@onResponse)
|
23
|
+
|
24
|
+
onResponse: (resp) =>
|
25
|
+
@popover?.destroy()
|
26
|
+
@setLoading(false)
|
27
|
+
@setNewElement($(resp))
|
28
|
+
|
29
|
+
setNewElement: ($el) ->
|
30
|
+
@$el.tooltip('hide')
|
31
|
+
@$el.replaceWith($el)
|
32
|
+
@setElement($el)
|
33
|
+
# Open popover if provided
|
34
|
+
if (content = $el.data('popover-content')) then @openPopover(content)
|
35
|
+
|
36
|
+
openPopover: (content) ->
|
37
|
+
@popover = new Stall.WishListFormPopover(content: content)
|
38
|
+
@popover.renderTo(@$el)
|
39
|
+
@listenTo(@popover, 'send', => @setLoading(true))
|
40
|
+
@listenTo(@popover, 'added', @onResponse)
|
41
|
+
|
42
|
+
setLoading: (state) ->
|
43
|
+
@popover?.hide()
|
44
|
+
@$el.toggleClass('loading', state)
|
45
|
+
@$('[data-wish-list-icon]').toggleClass('hidden', state)
|
46
|
+
@$('[data-wish-list-loading-spinner]').toggleClass('hidden', !state)
|
47
|
+
|
48
|
+
class Stall.WishListFormPopover extends Vertebra.View
|
49
|
+
events:
|
50
|
+
'ajax:beforeSend [data-add-to-wish-list-form]': 'onBeforeSend'
|
51
|
+
'ajax:success [data-add-to-wish-list-form]': 'onItemAdded'
|
52
|
+
'click [data-cancel-button]': 'onCancelClicked'
|
53
|
+
|
54
|
+
initialize: (options = {}) ->
|
55
|
+
@content = options.content
|
56
|
+
|
57
|
+
renderTo: ($parent) ->
|
58
|
+
$parent.popover
|
59
|
+
title: false
|
60
|
+
content: @content
|
61
|
+
html: true
|
62
|
+
trigger: 'manual'
|
63
|
+
placement: 'auto'
|
64
|
+
container: 'body'
|
65
|
+
|
66
|
+
@popover = $parent.data('bs.popover')
|
67
|
+
@popover.show()
|
68
|
+
|
69
|
+
@setElement(@popover.$tip)
|
70
|
+
@initializeForm()
|
71
|
+
|
72
|
+
initializeForm: ->
|
73
|
+
@$('[data-variant-select-input]').each (i, el) ->
|
74
|
+
new Stall.VariantSelectInput(el: el)
|
75
|
+
|
76
|
+
hide: ->
|
77
|
+
@popover?.hide()
|
78
|
+
|
79
|
+
onBeforeSend: ->
|
80
|
+
@trigger('send')
|
81
|
+
|
82
|
+
onItemAdded: (e, resp) ->
|
83
|
+
@trigger('added', resp)
|
84
|
+
|
85
|
+
onCancelClicked: ->
|
86
|
+
console.log 'onCancelClicked', this
|
87
|
+
@destroy()
|
88
|
+
|
89
|
+
destroy: ->
|
90
|
+
return if @destroyed
|
91
|
+
@popover.destroy()
|
92
|
+
@popover = null
|
93
|
+
super()
|
94
|
+
@destroyed = true
|
95
|
+
|
96
|
+
|
97
|
+
Stall.onDomReady ->
|
98
|
+
$('[data-add-to-wish-list="product"]').each (i, el) ->
|
99
|
+
new Stall.AddProductToWishListButton(el: el)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Stall.AddToWishListButton extends Vertebra.View
|
2
|
+
events:
|
3
|
+
'click': 'add'
|
4
|
+
|
5
|
+
initialize: ->
|
6
|
+
@url = @$el.data('url')
|
7
|
+
|
8
|
+
add: ->
|
9
|
+
data = @$el.closest('form').serialize()
|
10
|
+
$.post(@url, data).then(@onResponse)
|
11
|
+
|
12
|
+
onResponse: (resp) =>
|
13
|
+
$(resp).modal()
|
14
|
+
|
15
|
+
Stall.onDomReady ->
|
16
|
+
$('[data-add-to-wish-list="line-item-form"]').each (i, el) ->
|
17
|
+
new Stall.AddToWishListButton(el: el)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Stall.ProductListForm extends Vertebra.View
|
2
|
+
events:
|
3
|
+
'change [data-quantity-field]': 'formUpdated'
|
4
|
+
'cocoon:after-remove': 'formUpdated'
|
5
|
+
'ajax:success': 'updateSuccess'
|
6
|
+
|
7
|
+
initialize: ->
|
8
|
+
@clean()
|
9
|
+
|
10
|
+
clean: ->
|
11
|
+
@$('[data-product-list-update-button]').hide(0)
|
12
|
+
# Backwards compatibility with app overriden cart forms
|
13
|
+
@$('[data-cart-update-button]').hide(0)
|
14
|
+
|
15
|
+
formUpdated: (e) ->
|
16
|
+
@$el.submit()
|
17
|
+
|
18
|
+
updateSuccess: (e, resp) ->
|
19
|
+
@updateProductListFormWith(resp)
|
20
|
+
|
21
|
+
updateProductListFormWith: (markup) =>
|
22
|
+
$form = $(markup).find('[data-product-list-form], [data-cart-form]')
|
23
|
+
@$el.html($form.html())
|
24
|
+
@clean()
|
25
|
+
|
26
|
+
|
27
|
+
Stall.onDomReady ->
|
28
|
+
# Backwards compatibility with app overriden cart forms
|
29
|
+
if ($product_listForm = $('[data-product-list-form], [data-cart-form]')).length
|
30
|
+
product_listForm = new Stall.ProductListForm(el: $product_listForm)
|
31
|
+
$product_listForm.data('stall.product-list-form', product_listForm)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Stall
|
2
|
+
class CartLineItemsController < Stall::LineItemsController
|
3
|
+
def create
|
4
|
+
super do |success|
|
5
|
+
if success
|
6
|
+
@widget_partial = render_to_string(partial: 'stall/carts/widget', locals: { cart: product_list })
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def product_list
|
14
|
+
@product_list ||= ProductList.find_by_token(params[:cart_id]) || current_cart
|
15
|
+
end
|
16
|
+
|
17
|
+
def service
|
18
|
+
@service ||= Stall.config.service_for(:add_to_cart).new(product_list, params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -5,6 +5,12 @@ module Stall
|
|
5
5
|
def show
|
6
6
|
@curated_product_list = CuratedProductList.friendly.find(params[:id])
|
7
7
|
search_products_among(@curated_product_list.products)
|
8
|
+
|
9
|
+
# Also select curated list product positions to allow distinct call to
|
10
|
+
# work when ordering results by position
|
11
|
+
@products = @products.select('stall_products.*, stall_curated_list_products.position')
|
12
|
+
|
13
|
+
add_breadcrumb(@curated_product_list)
|
8
14
|
end
|
9
15
|
end
|
10
16
|
end
|
@@ -1,23 +1,30 @@
|
|
1
1
|
module Stall
|
2
2
|
class LineItemsController < Stall::ApplicationController
|
3
3
|
def create
|
4
|
-
service = Stall.config.service_for(:add_to_cart).new(cart, params)
|
5
|
-
|
6
4
|
if service.call
|
7
5
|
@quantity = params[:line_item][:quantity].to_i
|
8
6
|
@line_item = service.line_item
|
9
|
-
|
10
|
-
|
7
|
+
# Allow subclasses to hook into successful product list add
|
8
|
+
yield(true) if block_given?
|
9
|
+
# We do not render if the yield bock already has done it
|
10
|
+
render partial: 'added' unless response_body
|
11
11
|
else
|
12
12
|
@line_item = service.line_item
|
13
|
-
|
13
|
+
# Allow subclasses to hook into failed product list add
|
14
|
+
yield(false) if block_given?
|
15
|
+
# We do not render if the yield bock already has done it
|
16
|
+
render partial: 'add_error' unless response_body
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
17
20
|
private
|
18
21
|
|
19
|
-
def
|
20
|
-
|
22
|
+
def product_list
|
23
|
+
fail NotImplementedError, 'Override #product_list in subclass'
|
24
|
+
end
|
25
|
+
|
26
|
+
def service
|
27
|
+
fail NotImplementedError, 'Override #service in subclass'
|
21
28
|
end
|
22
29
|
end
|
23
30
|
end
|
@@ -3,7 +3,14 @@ module Stall
|
|
3
3
|
private
|
4
4
|
|
5
5
|
def add_product_breadcrumbs
|
6
|
-
|
6
|
+
if @curated_product_list
|
7
|
+
add_breadcrumb(@curated_product_list)
|
8
|
+
elsif @manufacturer
|
9
|
+
add_breadcrumb(@manufacturer)
|
10
|
+
else
|
11
|
+
add_product_category_breadcrumbs(@product.product_category)
|
12
|
+
end
|
13
|
+
|
7
14
|
add_breadcrumb(@product)
|
8
15
|
end
|
9
16
|
|
@@ -3,6 +3,8 @@ module Stall
|
|
3
3
|
include ProductsSearch
|
4
4
|
include ProductsBreadcrumbs
|
5
5
|
|
6
|
+
before_action :load_parent_data
|
7
|
+
|
6
8
|
def index
|
7
9
|
search_products_among(Product.all)
|
8
10
|
|
@@ -16,5 +18,15 @@ module Stall
|
|
16
18
|
|
17
19
|
add_product_breadcrumbs
|
18
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def load_parent_data
|
25
|
+
if params[:curated_product_list_id]
|
26
|
+
@curated_product_list = CuratedProductList.find(params[:curated_product_list_id])
|
27
|
+
elsif params[:manufacturer_id]
|
28
|
+
@manufacturer = Manufacturer.find(params[:manufacturer_id])
|
29
|
+
end
|
30
|
+
end
|
19
31
|
end
|
20
32
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Stall
|
2
|
+
class WishListLineItemsController < Stall::LineItemsController
|
3
|
+
include Stall::AddToWishListHelper
|
4
|
+
|
5
|
+
def create
|
6
|
+
if !service.line_item_params? && (product_id = params[:product_id]).present?
|
7
|
+
product = Product.find(product_id)
|
8
|
+
|
9
|
+
if product.variants.length > 1
|
10
|
+
form = render_to_string(
|
11
|
+
partial: 'stall/wish_list_line_items/form', locals: {
|
12
|
+
wish_list: product_list, line_item: LineItem.new, product: product
|
13
|
+
}
|
14
|
+
)
|
15
|
+
|
16
|
+
add_to_wish_list_button(product, {
|
17
|
+
wish_list: product_list, popover_content: form
|
18
|
+
})
|
19
|
+
else
|
20
|
+
service.add(product.variants.first)
|
21
|
+
add_to_wish_list_button(product, wish_list: product_list)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
super do |valid|
|
25
|
+
if (product = @line_item.sellable.try(:product))
|
26
|
+
add_to_wish_list_button(product, wish_list: product_list)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy
|
33
|
+
line_item = product_list.line_items.find(params[:id])
|
34
|
+
product = line_item.sellable.try(:product)
|
35
|
+
product_list.line_items.destroy(line_item)
|
36
|
+
|
37
|
+
if product
|
38
|
+
add_to_wish_list_button(product, wish_list: product_list)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def product_list
|
45
|
+
@product_list ||= ProductList.find_by_token(params[:cart_id]) || current_wish_list
|
46
|
+
end
|
47
|
+
|
48
|
+
def service
|
49
|
+
@service ||= Stall.config.service_for(:add_to_wish_list).new(product_list, params)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module Stall
|
2
2
|
module AddToCartHelper
|
3
3
|
def add_to_cart_form_for(sellable, cart: nil)
|
4
|
-
render partial: 'stall/
|
4
|
+
render partial: 'stall/cart_line_items/form', locals: {
|
5
5
|
cart: (cart || current_cart),
|
6
|
-
line_item: LineItem.new(sellable: sellable)
|
6
|
+
line_item: LineItem.new(sellable: sellable),
|
7
|
+
product: sellable
|
7
8
|
}
|
8
9
|
end
|
9
10
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Stall
|
2
|
+
module AddToWishListHelper
|
3
|
+
def add_to_wish_list_button(product, variant: nil, wish_list: current_wish_list, popover_content: nil)
|
4
|
+
included = wish_list.includes_product?(product)
|
5
|
+
|
6
|
+
url = if included
|
7
|
+
line_item = wish_list.line_item_for_product(product)
|
8
|
+
wish_list_line_item_path(current_wish_list, line_item)
|
9
|
+
else
|
10
|
+
wish_list_line_items_path(current_wish_list)
|
11
|
+
end
|
12
|
+
|
13
|
+
title = if included
|
14
|
+
t('stall.wish_list_line_items.form.remove')
|
15
|
+
else
|
16
|
+
t('stall.wish_list_line_items.form.add')
|
17
|
+
end
|
18
|
+
|
19
|
+
render partial: 'stall/wish_list_line_items/button', locals: {
|
20
|
+
product: product,
|
21
|
+
variant: variant,
|
22
|
+
cart: wish_list,
|
23
|
+
included: included,
|
24
|
+
url: url,
|
25
|
+
title: title,
|
26
|
+
popover_content: popover_content
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Stall
|
2
|
+
module ProductsHelper
|
3
|
+
def product_path(*args)
|
4
|
+
if @curated_product_list
|
5
|
+
curated_product_list_product_path(@curated_product_list, *args)
|
6
|
+
elsif @manufacturer
|
7
|
+
manufacturer_product_path(@manufacturer, *args)
|
8
|
+
else
|
9
|
+
Rails.application.routes.url_helpers.product_path(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -14,12 +14,21 @@ module Stall
|
|
14
14
|
before_validation :ensure_user_email
|
15
15
|
|
16
16
|
has_many :product_lists, dependent: :destroy
|
17
|
+
has_many :wish_lists, class_name: 'WishList'
|
17
18
|
has_many :credit_notes, dependent: :destroy
|
18
19
|
has_many :carts, dependent: :nullify
|
19
20
|
|
20
21
|
validates :email, presence: true,
|
21
22
|
format: { with: /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/ }
|
22
23
|
|
24
|
+
def name
|
25
|
+
if billing_address && (billing_address.first_name.present? || billing_address.last_name.present?)
|
26
|
+
[billing_address.last_name, billing_address.first_name].join(' ')
|
27
|
+
else
|
28
|
+
email
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
23
32
|
def user_or_default
|
24
33
|
user || build_user
|
25
34
|
end
|