solidus_address_book 1.0.0

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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +23 -0
  3. data/README.md +49 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/javascripts/spree/frontend/solidus_address_book.js.erb +59 -0
  6. data/app/assets/stylesheets/spree/frontend/solidus_address_book.scss +92 -0
  7. data/app/controllers/spree/addresses_controller.rb +84 -0
  8. data/app/controllers/spree/checkout_controller_decorator.rb +65 -0
  9. data/app/helpers/spree/addresses_helper.rb +39 -0
  10. data/app/models/concerns/solidus_address_book/address.rb +18 -0
  11. data/app/models/concerns/solidus_address_book/permitted_attributes_concern.rb +11 -0
  12. data/app/models/permitted_attributes_decorator.rb +1 -0
  13. data/app/models/spree/address_ability.rb +15 -0
  14. data/app/models/spree/address_decorator.rb +83 -0
  15. data/app/models/spree/order_decorator.rb +109 -0
  16. data/app/models/spree/user_decorator.rb +11 -0
  17. data/app/overrides/add_address_book_to_my_account.rb +9 -0
  18. data/app/views/spree/addresses/_form.html.erb +114 -0
  19. data/app/views/spree/addresses/destroy.js.erb +2 -0
  20. data/app/views/spree/addresses/edit.html.erb +29 -0
  21. data/app/views/spree/addresses/new.html.erb +29 -0
  22. data/app/views/spree/checkout/_address.html.erb +75 -0
  23. data/app/views/spree/users/_addresses.html.erb +36 -0
  24. data/config/locales/de.yml +15 -0
  25. data/config/locales/en.yml +28 -0
  26. data/config/locales/pt-BR.yml +3 -0
  27. data/config/locales/ru.yml +13 -0
  28. data/config/routes.rb +9 -0
  29. data/db/migrate/20110302102208_add_user_id_and_deleted_at_to_addresses.rb +13 -0
  30. data/db/migrate/20190619191704_add_label_to_addresses.rb +7 -0
  31. data/lib/generators/solidus_address_book/install_generator.rb +30 -0
  32. data/lib/solidus_address_book.rb +6 -0
  33. data/lib/solidus_address_book/configuration.rb +8 -0
  34. data/lib/solidus_address_book/engine.rb +24 -0
  35. data/lib/solidus_address_book/version.rb +5 -0
  36. metadata +354 -0
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'discard'
4
+
5
+ module SolidusAddressBook
6
+ module Address
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ belongs_to :user, class_name: Spree.user_class.to_s
11
+
12
+ include Discard::Model
13
+ self.discard_column = :deleted_at
14
+
15
+ scope :with_deleted, -> { where.not(deleted_at: nil) }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module SolidusAddressBook
2
+ module PermittedAttributesConcern
3
+ def address_attributes
4
+ super | [:label]
5
+ end
6
+
7
+ def checkout_attributes
8
+ super | [:bill_address_id, :ship_address_id]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ Spree::PermittedAttributes.singleton_class.prepend SolidusAddressBook::PermittedAttributesConcern
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Spree::AddressAbility
4
+ include CanCan::Ability
5
+
6
+ def initialize(user)
7
+ can :manage, Spree::Address do |address|
8
+ address.user == user
9
+ end
10
+
11
+ can :create, Spree::Address do |_address|
12
+ user.id.present?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'discard'
4
+
5
+ module AddressDecorator
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ prepend(InstanceMethods)
10
+
11
+ include Discard::Model
12
+ self.discard_column = :deleted_at
13
+
14
+ def self.required_fields
15
+ Spree::Address.validators.map do |v|
16
+ v.kind_of?(ActiveModel::Validations::PresenceValidator) ? v.attributes : []
17
+ end.flatten
18
+ end
19
+
20
+ def self.default(user = nil, kind = 'bill')
21
+ if user && user_address = user.public_send(:"#{kind}_address")
22
+ user_address.clone
23
+ else
24
+ build_default
25
+ end
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ # TODO: look into if this is actually needed. I don't want to override methods unless it is really needed
31
+ # can modify an address if it's not been used in an order
32
+ def same_as?(other)
33
+ return false if other.nil?
34
+
35
+ attributes.except('id', 'updated_at', 'created_at', 'user_id') == other.attributes.except('id', 'updated_at', 'created_at', 'user_id')
36
+ end
37
+
38
+ # can modify an address if it's not been used in an completed order
39
+ def editable?
40
+ new_record? || (Spree::Order.complete.where("bill_address_id = ? OR
41
+ ship_address_id = ? AND
42
+ state != 'complete' AND
43
+ shipment_state != 'shipped'", id, id).count == 0)
44
+ end
45
+
46
+ def can_be_deleted?
47
+ Spree::Order.where("bill_address_id = ? OR
48
+ ship_address_id = ? AND
49
+ state != 'complete' AND
50
+ shipment_state != 'shipped'", id, id).count == 0
51
+ end
52
+
53
+ def to_s
54
+ [
55
+ full_name,
56
+ company.to_s,
57
+ address1.to_s,
58
+ address2.to_s,
59
+ "#{city}, #{state ? state.abbr : state_name} #{zipcode}",
60
+ country.to_s
61
+ ].reject(&:empty?).join("<br/>").html_safe
62
+ end
63
+
64
+ # Remove readonly validation to be able to modify addresses over the flow
65
+ def readonly?
66
+ false
67
+ end
68
+
69
+ # Use discard ability to not affect order created previously
70
+ def destroy
71
+ can_be_deleted? ? delete : discard
72
+ end
73
+
74
+ def check
75
+ attrs = attributes.except('id', 'updated_at', 'created_at')
76
+ the_same_address = user.addresses.where(attrs).first
77
+ the_same_address || self
78
+ end
79
+ end
80
+ end
81
+
82
+ Spree::Address.include(SolidusAddressBook::Address)
83
+ Spree::Address.include(AddressDecorator)
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrderDecorator
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ prepend(InstanceMethods)
8
+ before_validation :clone_shipping_address
9
+ end
10
+
11
+ module InstanceMethods
12
+ def clone_shipping_address
13
+ return unless SolidusAddressBook::Config[:disable_bill_address]
14
+
15
+ bill_address = ship_address if ship_address
16
+
17
+ true
18
+ end
19
+
20
+ def clone_billing_address
21
+ if bill_address
22
+ ship_address = bill_address
23
+ end
24
+ true
25
+ end
26
+
27
+ def bill_address_id=(id)
28
+ address = Spree::Address.where(id: id).first
29
+
30
+ if address && address.user_id == user_id
31
+ self["bill_address_id"] = address.id
32
+ user.update_attribute(:bill_address_id, address.id)
33
+ bill_address.reload
34
+ else
35
+ self["bill_address_id"] = nil
36
+ end
37
+ end
38
+
39
+ def bill_address_attributes=(attributes)
40
+ bill_address = update_or_create_address(attributes)
41
+ user.bill_address = bill_address if user
42
+ end
43
+
44
+ def ship_address_id=(id)
45
+ address = Spree::Address.where(id: id).first
46
+
47
+ if address && address.user_id == user_id
48
+ self["ship_address_id"] = address.id
49
+ user.update_attribute(:ship_address_id, address.id)
50
+ ship_address.reload
51
+ else
52
+ self["ship_address_id"] = nil
53
+ end
54
+ end
55
+
56
+ def ship_address_attributes=(attributes)
57
+ ship_address = update_or_create_address(attributes)
58
+ user.ship_address = ship_address if user
59
+ end
60
+
61
+ def assign_default_addresses!
62
+ if user
63
+ bill_address = user.bill_address if !bill_address_id && user.bill_address.try(:valid?)
64
+ # Skip setting ship address if order doesn't have a delivery checkout step
65
+ # to avoid triggering validations on shipping address
66
+ ship_address = user.ship_address if !ship_address_id && user.ship_address.try(:valid?) && checkout_steps.include?("delivery")
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def update_addresses_params
73
+ bill_address_attributes = @updating_params["order"].delete("bill_address_attributes")
74
+ bill_address_id = @updating_params["order"].delete("bill_address_id")
75
+ ship_address_attributes = @updating_params["order"].delete("ship_address_attributes")
76
+ ship_address_id = @updating_params["order"].delete("ship_address_id")
77
+ end
78
+
79
+ def update_or_create_address(attributes = {})
80
+ return if attributes.blank?
81
+
82
+ if user
83
+ address = user.addresses.build(attributes.except(:id)).check
84
+ return address if address.id
85
+ end
86
+
87
+ if attributes[:id]
88
+ address = Spree::Address.find(attributes[:id])
89
+ attributes.delete(:id)
90
+
91
+ if address&.editable?
92
+ address.update_attributes(attributes)
93
+ return address
94
+ else
95
+ attributes.delete(:id)
96
+ end
97
+ end
98
+
99
+ if !attributes[:id]
100
+ address = Spree::Address.new(attributes)
101
+ address.save
102
+ end
103
+
104
+ address
105
+ end
106
+ end
107
+ end
108
+
109
+ Spree::Order.include(OrderDecorator)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UserDecorator
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :addresses, class_name: 'Spree::Address'
8
+ end
9
+ end
10
+
11
+ Spree.user_class.include(UserDecorator)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Deface::Override.new(
4
+ virtual_path: 'spree/users/show',
5
+ name: 'address_book_account_my_orders',
6
+ insert_after: "[data-hook='account_my_orders']",
7
+ partial: 'spree/users/addresses',
8
+ disabled: false
9
+ )
@@ -0,0 +1,114 @@
1
+ <% address_id = address_type.chars.first %>
2
+
3
+ <div class="inner" data-hook=<%="#{address_type}_inner" %>>
4
+ <div class="field" id=<%="#{address_id}label" %>>
5
+ <%= address_form.label :label, t('spree.label') %>
6
+ <%= address_form.text_field :label, class: 'required', autocomplete: address_type + ' label', autofocus: true, placeholder: t('spree.placeholder.address.label') %>
7
+ </div>
8
+
9
+ <div class="field field-required" id=<%="#{address_id}firstname" %>>
10
+ <%= address_form.label :firstname, t('spree.first_name') %>
11
+ <%= address_form.text_field :firstname, class: 'required', autocomplete: address_type + ' given-name', required: true %>
12
+ </div>
13
+
14
+ <div class="field" id=<%="#{address_id}lastname" %>>
15
+ <%= address_form.label :lastname, t('spree.last_name') %>
16
+ <%= address_form.text_field :lastname, autocomplete: address_type + ' family-name' %>
17
+ </div>
18
+
19
+ <% if Spree::Config[:company] %>
20
+ <div class="field" id=<%="#{address_id}company" %>>
21
+ <%= address_form.label :company, t('spree.company') %>
22
+ <%= address_form.text_field :company, autocomplete: address_type + ' organization' %>
23
+ </div>
24
+ <% end %>
25
+
26
+ <div class="field field-required" id=<%="#{address_id}address1" %>>
27
+ <%= address_form.label :address1, t('spree.street_address') %>
28
+ <%= address_form.text_field :address1, class: 'required', autocomplete: address_type + ' address-line1', required: true %>
29
+ </div>
30
+
31
+ <div class="field" id=<%="#{address_id}address2" %>>
32
+ <%= address_form.label :address2, I18n.t('spree.street_address_2') %>
33
+ <%= address_form.text_field :address2, autocomplete: address_type + ' address-line2' %>
34
+ </div>
35
+
36
+ <div class="field field-required" id=<%="#{address_id}city" %>>
37
+ <%= address_form.label :city, t('spree.city') %>
38
+ <%= address_form.text_field :city, class: 'required', autocomplete: address_type + ' address-level2', required: true %>
39
+ </div>
40
+
41
+ <div class="field field-required" id=<%="#{address_id}country" %>>
42
+ <%= address_form.label :country_id, t('spree.country') %>
43
+ <span id=<%="#{address_id}country-selection" %>>
44
+ <%= address_form.collection_select :country_id, available_countries, :id, :name, {},
45
+ class: 'required',
46
+ autocomplete: address_type + ' country-name',
47
+ required: true
48
+ %>
49
+ </span>
50
+ </div>
51
+
52
+ <% if Spree::Config[:address_requires_state] %>
53
+ <div class="field field-required" id=<%="#{address_id}state" %>>
54
+ <% have_states = !address.country.states.empty? %>
55
+ <%= address_form.label :state, t('spree.state') %>
56
+
57
+ <span class="js-address-fields" style="display: none;">
58
+ <%=
59
+ address_form.collection_select(
60
+ :state_id, address.country.states, :id, :name,
61
+ {include_blank: true},
62
+ {
63
+ class: have_states ? 'required' : '',
64
+ style: have_states ? '' : 'display: none;',
65
+ disabled: !have_states,
66
+ autocomplete: address_type + ' address-level1',
67
+ })
68
+ %>
69
+ <%=
70
+ address_form.text_field(
71
+ :state_name,
72
+ class: !have_states ? 'required' : '',
73
+ style: have_states ? 'display: none;' : '',
74
+ disabled: have_states,
75
+ autocomplete: address_type + ' address-level1',
76
+ )
77
+ %>
78
+ </span>
79
+
80
+ <noscript>
81
+ <%= address_form.text_field :state_name, class: 'required', autocomplete: address_type + ' address-level1', required: true %>
82
+ </noscript>
83
+ </div>
84
+ <% end %>
85
+
86
+ <div class="field <%= 'field-required' if address.require_zipcode? %>" id=<%="#{address_id}zipcode" %>>
87
+ <%= address_form.label :zipcode, t('spree.zip') %>
88
+ <%= address_form.text_field :zipcode, class: "#{'required' if address.require_zipcode?}", autocomplete: address_type + ' postal-code', required: true %>
89
+ </div>
90
+
91
+ <div class="field <%= 'field-required' if address.require_phone? %>" id=<%="#{address_id}phone" %>>
92
+ <%= address_form.label :phone, t('spree.phone') %>
93
+ <% phone_hash = address.require_phone? ? { class: 'required', required: true } : {} %>
94
+ <%= address_form.phone_field :phone, phone_hash.merge({ autocomplete: address_type + ' home tel' }) %>
95
+ </div>
96
+
97
+ <% if Spree::Config[:alternative_shipping_phone] %>
98
+ <div class="field" id=<%="#{address_id}altphone" %>>
99
+ <%= address_form.label :alternative_phone, t('spree.alternative_phone') %>
100
+ <%= address_form.phone_field :alternative_phone, autocomplete: address_type + ' tel'%>
101
+ </div>
102
+ <% end %>
103
+
104
+ <% if params[:controller] == 'spree/checkout' %>
105
+ <div class="field" id=<%="#{address_id}save" %>>
106
+ <p class="field checkbox" data-hook="save_user_address">
107
+ <%= label_tag :save_user_address, id: 'save_user_address' do %>
108
+ <%= check_box_tag 'save_user_address', '1' %>
109
+ <%= t('spree.save_address') %>
110
+ <% end %>
111
+ </p>
112
+ </div>
113
+ <% end %>
114
+ </div>
@@ -0,0 +1,2 @@
1
+ $("#billing_address_<%= @address.id %>").fadeOut();
2
+ $("#shipping_address_<%= @address.id %>").fadeOut();
@@ -0,0 +1,29 @@
1
+ <div class="col-md-6 col-md-offset-3">
2
+ <div class="panel panel-default">
3
+ <div class="panel-heading">
4
+ <h3 class="panel-title"><%= t(:edit_shipping_address, scope: :spree) %></h3>
5
+ </div>
6
+
7
+ <div class="panel-body">
8
+ <%= render 'spree/shared/error_messages', target: @address %>
9
+
10
+ <%= form_for @address, html: { id: 'checkout_form_address' } do |f| %>
11
+ <fieldset>
12
+ <div class="inner">
13
+ <%= render 'spree/addresses/form',
14
+ address_name: 'address',
15
+ address_form: f,
16
+ address_type: 'shipping',
17
+ address: @address
18
+ %>
19
+ </div>
20
+
21
+ <%= f.submit t('spree.update'), class: 'btn btn-primary' %>
22
+ <% if try_spree_current_user && respond_to?(:account_path) %>
23
+ <%= link_to t('spree.back_to_my_account'), spree.account_path, class: 'btn btn-primary' %>
24
+ <% end %>
25
+ </fieldset>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+ </div>
@@ -0,0 +1,29 @@
1
+ <div class="col-md-6 col-md-offset-3">
2
+ <div class="panel panel-default">
3
+ <div class="panel-heading">
4
+ <h3 class="panel-title"><%= t(:new_shipping_address, scope: :spree) %></h3>
5
+ </div>
6
+
7
+ <div class="panel-body">
8
+ <%= render 'spree/shared/error_messages', target: @address %>
9
+
10
+ <%= form_for @address, html: { id: 'checkout_form_address' } do |f| %>
11
+ <fieldset>
12
+ <div class="inner">
13
+ <%= render 'spree/addresses/form',
14
+ address_name: 'address',
15
+ address_form: f,
16
+ address_type: 'shipping',
17
+ address: @address
18
+ %>
19
+ </div>
20
+
21
+ <%= f.submit t('spree.save'), class: 'btn btn-primary' %>
22
+ <% if try_spree_current_user && respond_to?(:account_path) %>
23
+ <%= link_to t('spree.back_to_my_account'), spree.account_path, class: 'btn btn-primary' %>
24
+ <% end %>
25
+ </fieldset>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+ </div>