cartify 0.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +32 -0
- data/app/assets/config/cartify_manifest.js +2 -0
- data/app/assets/javascripts/cartify/application.js +14 -0
- data/app/assets/javascripts/cartify/checkout.js +11 -0
- data/app/assets/javascripts/cartify/masks.js +10 -0
- data/app/assets/javascripts/cartify/orders.js +7 -0
- data/app/assets/stylesheets/cartify/application.css +15 -0
- data/app/concerns/cartify/authenticatable.rb +17 -0
- data/app/concerns/cartify/current_session.rb +52 -0
- data/app/concerns/cartify/showable.rb +41 -0
- data/app/concerns/cartify/updatable.rb +39 -0
- data/app/controllers/cartify/addresses_controller.rb +18 -0
- data/app/controllers/cartify/application_controller.rb +6 -0
- data/app/controllers/cartify/carts_controller.rb +20 -0
- data/app/controllers/cartify/checkout_controller.rb +29 -0
- data/app/controllers/cartify/order_items_controller.rb +35 -0
- data/app/controllers/cartify/orders_controller.rb +17 -0
- data/app/decorator/cartify/order_decorator.rb +39 -0
- data/app/forms/cartify/addresses_form.rb +71 -0
- data/app/helpers/cartify/application_helper.rb +18 -0
- data/app/helpers/cartify/carts_helper.rb +10 -0
- data/app/helpers/cartify/checkout_helper.rb +22 -0
- data/app/helpers/cartify/order_helper.rb +12 -0
- data/app/models/cartify/address.rb +23 -0
- data/app/models/cartify/application_record.rb +5 -0
- data/app/models/cartify/billing.rb +4 -0
- data/app/models/cartify/coupon.rb +10 -0
- data/app/models/cartify/credit_card.rb +25 -0
- data/app/models/cartify/delivery.rb +8 -0
- data/app/models/cartify/order.rb +64 -0
- data/app/models/cartify/order_item.rb +39 -0
- data/app/models/cartify/order_status.rb +18 -0
- data/app/models/cartify/shipping.rb +4 -0
- data/app/queries/cartify/orders_query.rb +17 -0
- data/app/views/cartify/addresses/index.haml +9 -0
- data/app/views/cartify/carts/_order_items.haml +17 -0
- data/app/views/cartify/carts/_order_summary.haml +15 -0
- data/app/views/cartify/carts/_quantity.haml +7 -0
- data/app/views/cartify/carts/_xs_order_items.haml +24 -0
- data/app/views/cartify/carts/show.html.haml +22 -0
- data/app/views/cartify/checkout/_order_summary.haml +4 -0
- data/app/views/cartify/checkout/_progress.haml +21 -0
- data/app/views/cartify/checkout/addresses.haml +35 -0
- data/app/views/cartify/checkout/complete.haml +19 -0
- data/app/views/cartify/checkout/confirm.haml +27 -0
- data/app/views/cartify/checkout/delivery.haml +11 -0
- data/app/views/cartify/checkout/login.haml +39 -0
- data/app/views/cartify/checkout/partials/_addresses_short.haml +12 -0
- data/app/views/cartify/checkout/partials/_delivery-lg.haml +22 -0
- data/app/views/cartify/checkout/partials/_delivery-xs.haml +22 -0
- data/app/views/cartify/checkout/partials/_list_of_items.haml +54 -0
- data/app/views/cartify/checkout/payment.haml +27 -0
- data/app/views/cartify/order_items/create.js.erb +5 -0
- data/app/views/cartify/order_items/destroy.js.erb +3 -0
- data/app/views/cartify/order_items/update.js.erb +3 -0
- data/app/views/cartify/orders/_filters.haml +16 -0
- data/app/views/cartify/orders/_orders-lg.haml +10 -0
- data/app/views/cartify/orders/_orders-xs.haml +23 -0
- data/app/views/cartify/orders/index.haml +21 -0
- data/app/views/cartify/orders/show.haml +28 -0
- data/app/views/cartify/shared/_address.haml +28 -0
- data/app/views/cartify/shared/_addresses_form.haml +31 -0
- data/app/views/cartify/shared/_checkout_summary_numbers.haml +15 -0
- data/app/views/cartify/shared/_order_summary_numbers.haml +15 -0
- data/config/locales/en.yml +135 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20171005130857_create_cartify_coupons.rb +8 -0
- data/db/migrate/20171005131198_create_cartify_credit_cards.rb +12 -0
- data/db/migrate/20171005131199_create_cartify_deliveries.rb +9 -0
- data/db/migrate/20171005131200_create_cartify_order_statuses.rb +7 -0
- data/db/migrate/20171005131201_create_cartify_orders.rb +19 -0
- data/db/migrate/20171005131202_create_cartify_order_items.rb +14 -0
- data/db/migrate/20171006133037_create_cartify_addresses.rb +17 -0
- data/lib/cartify.rb +19 -0
- data/lib/cartify/engine.rb +30 -0
- data/lib/cartify/version.rb +5 -0
- data/lib/generators/initializer/USAGE +49 -0
- data/lib/generators/initializer/initializer_generator.rb +17 -0
- data/lib/generators/initializer/templates/initializer.rb +10 -0
- data/lib/tasks/cartify_tasks.rake +4 -0
- metadata +478 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require_dependency 'cartify/application_controller'
|
|
2
|
+
|
|
3
|
+
module Cartify
|
|
4
|
+
class OrdersController < ApplicationController
|
|
5
|
+
before_action :cartify_authenticate_user!
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
all_in_one = cartify_current_user.orders.includes(:delivery, :order_status, :order_items)
|
|
9
|
+
@orders = Cartify::OrdersQuery.new(all_in_one).run(params[:filter]).decorate
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@order = cartify_current_user.orders.find(params[:id]).decorate
|
|
14
|
+
redirect_to checkout_path(:confirm) if @order.status == t('order.status.in_progress')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class OrderDecorator < Draper::Decorator
|
|
3
|
+
delegate_all
|
|
4
|
+
ORDER_STATUSES = {
|
|
5
|
+
in_progress: I18n.t('order.status.in_progress'),
|
|
6
|
+
in_queue: I18n.t('order.status.in_queue'),
|
|
7
|
+
in_delivery: I18n.t('order.status.in_delivery'),
|
|
8
|
+
delivered: I18n.t('order.status.delivered'),
|
|
9
|
+
canceled: I18n.t('order.status.canceled')
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
def complited_at
|
|
13
|
+
object.updated_at.strftime('%F')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def number
|
|
17
|
+
id = object.id.to_s
|
|
18
|
+
template = 'R0000000'
|
|
19
|
+
template[0..-id.size] + id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def status
|
|
23
|
+
ORDER_STATUSES[object.order_status.name.to_sym]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def creation_date
|
|
27
|
+
data = object.updated_at
|
|
28
|
+
I18n.t('date.month_names')[data.month] + " #{data.strftime('%d, %Y')}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sharp_number
|
|
32
|
+
"#{I18n.t('complete.order')} ##{number}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def secret_card_number
|
|
36
|
+
'** ** ** ' + object.credit_card.number.last(4)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class AddressesForm
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
include Virtus.model
|
|
5
|
+
|
|
6
|
+
attr_reader :params, :target, :use_billing
|
|
7
|
+
|
|
8
|
+
def initialize(params = false)
|
|
9
|
+
@save = false
|
|
10
|
+
@params = params
|
|
11
|
+
@target = Cartify::Order.find_by(id: order_id) ||
|
|
12
|
+
Cartify.user_class.find_by(id: user_id) ||
|
|
13
|
+
Cartify.user_class.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def save
|
|
17
|
+
@save = true
|
|
18
|
+
return false unless valid?
|
|
19
|
+
persist!
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def errors
|
|
24
|
+
{ billing: billing.errors, shipping: shipping.errors }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def billing
|
|
28
|
+
fresh_bill = target.addresses.find_or_initialize_by(type: 'Cartify::Billing')
|
|
29
|
+
fresh_bill.assign_attributes(params_for(:billing)) if save?
|
|
30
|
+
@billing ||= fresh_bill
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def shipping
|
|
34
|
+
fresh_shipp = target.addresses.find_or_initialize_by(type: 'Cartify::Shipping')
|
|
35
|
+
fresh_shipp.assign_attributes(params_for(:shipping)) if save?
|
|
36
|
+
@shipping ||= fresh_shipp
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def user_id
|
|
42
|
+
params.fetch(:user_id, false) || (params[:billing][:user_id] if nested?)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def order_id
|
|
46
|
+
params.fetch(:order_id, false) || (params[:billing][:order_id] if nested?)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def nested?
|
|
50
|
+
params.fetch(:billing, false)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def save?
|
|
54
|
+
@save
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def persist!
|
|
58
|
+
billing.save
|
|
59
|
+
shipping.save
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def valid?
|
|
63
|
+
billing.valid? && shipping.valid?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def params_for(type)
|
|
67
|
+
type = params[:use_billing] == '1' ? :billing : type
|
|
68
|
+
params.require(type).permit(:first_name, :last_name, :address, :city, :zip, :country, :phone, :user_id, :order_id)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
module ApplicationHelper
|
|
3
|
+
def shop_icon_quantity
|
|
4
|
+
qty = current_order.order_items.reload.collect(&:quantity).compact.sum
|
|
5
|
+
"<span class='shop-quantity'>#{qty}</span>".html_safe unless qty.zero?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def active_class(link_path)
|
|
9
|
+
return '' if request.GET.empty?
|
|
10
|
+
link_path.include? request.GET.first.join('=') ? 'active' : ''
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def country_name(object)
|
|
14
|
+
country = ISO3166::Country[object.country]
|
|
15
|
+
country.translations[I18n.locale.to_s] || country.name
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
module CheckoutHelper
|
|
3
|
+
def active_step?(current_step)
|
|
4
|
+
'active' if current_step == step
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def order_summary_text_position
|
|
8
|
+
return 'text-center general-text-right' if right?
|
|
9
|
+
'general-text-align'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def order_summary_table_position
|
|
13
|
+
'general-summary-table-right general-text-right' if right?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def right?
|
|
19
|
+
%i[confirm complete].include? step
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Cartify::OrderHelper
|
|
2
|
+
def order_active_filter
|
|
3
|
+
case request.GET[:filter]
|
|
4
|
+
when 'in_progress' then t('order.status.in_progress')
|
|
5
|
+
when 'in_queue' then t('order.status.in_queue')
|
|
6
|
+
when 'in_delivery' then t('order.status.in_delivery')
|
|
7
|
+
when 'delivered' then t('order.status.delivered')
|
|
8
|
+
when 'canceled' then t('order.status.canceled')
|
|
9
|
+
else t('order.status.all')
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class Address < ApplicationRecord
|
|
3
|
+
belongs_to :user, class_name: Cartify.user_class.to_s, optional: true
|
|
4
|
+
belongs_to :order, optional: true
|
|
5
|
+
|
|
6
|
+
scope :shipping, -> { where(type: 'Cartify::Shipping') }
|
|
7
|
+
scope :billing, -> { where(type: 'Cartify::Billing') }
|
|
8
|
+
|
|
9
|
+
validates :first_name, :last_name, :address, :city, :zip, :country, :phone, presence: true
|
|
10
|
+
validates_length_of :first_name, :last_name, :address, :city, :country, maximum: 49
|
|
11
|
+
validates :first_name, :last_name, format: { with: /\A[A-Za-z]{0,49}\z/ }
|
|
12
|
+
validates :city, :country, format: { with: /\A[A-Za-z\s]{0,49}\z/ }
|
|
13
|
+
validates :address, format: { with: /\A[-A-Za-z\s\d,]{0,49}\z/ }
|
|
14
|
+
|
|
15
|
+
validates_length_of :phone, maximum: 15
|
|
16
|
+
validates_length_of :zip, maximum: 10
|
|
17
|
+
before_validation :clear_mask
|
|
18
|
+
|
|
19
|
+
def clear_mask
|
|
20
|
+
phone&.gsub!(/[-\s()]/, '')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class Coupon < ApplicationRecord
|
|
3
|
+
validates :name, presence: true, uniqueness: true
|
|
4
|
+
validates :value, presence: true
|
|
5
|
+
validates :value, numericality: { only_float: true }
|
|
6
|
+
validates_length_of :name, is: 15
|
|
7
|
+
|
|
8
|
+
has_many :orders
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class CreditCard < ApplicationRecord
|
|
3
|
+
validates :number, :name, :mm_yy, :cvv, presence: true
|
|
4
|
+
validates_length_of :cvv, in: 3..4
|
|
5
|
+
validates :cvv, numericality: { only_integer: true }
|
|
6
|
+
validates_format_of :mm_yy, with: %r{\A(0[1-9]|10|11|12)\/\d\d\z}, message: I18n.t('validation.mm_yy')
|
|
7
|
+
validates_format_of :number, with: /\A\d{16}\z/, message: I18n.t('validation.cart_number')
|
|
8
|
+
validates_format_of :name, with: /\A[a-zA-Z\s]{0,49}\z/, message: I18n.t('validation.cart_name')
|
|
9
|
+
before_validation :clear_mask
|
|
10
|
+
has_one :order
|
|
11
|
+
|
|
12
|
+
after_save :connect_to_order
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def connect_to_order
|
|
17
|
+
user = CurrentSession.user unless CurrentSession.user.nil?
|
|
18
|
+
user&.orders&.where_status(:in_progress)&.first&.update_attributes(credit_card_id: id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clear_mask
|
|
22
|
+
number&.gsub!(/\s/, '')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class Order < ApplicationRecord
|
|
3
|
+
belongs_to :order_status
|
|
4
|
+
belongs_to :coupon, optional: true
|
|
5
|
+
belongs_to :user, class_name: Cartify.user_class.to_s, optional: true
|
|
6
|
+
belongs_to :delivery, optional: true
|
|
7
|
+
belongs_to :credit_card, optional: true
|
|
8
|
+
has_many :order_items, dependent: :destroy
|
|
9
|
+
has_many :addresses, dependent: :destroy
|
|
10
|
+
has_one :billing
|
|
11
|
+
has_one :shipping
|
|
12
|
+
has_many :books, through: :order_items
|
|
13
|
+
before_validation :set_order_status, on: :create
|
|
14
|
+
before_save :update_subtotal, :update_total, :connect_to_user
|
|
15
|
+
|
|
16
|
+
scope :where_status, ->(status_name) { joins(:order_status).where(cartify_order_statuses: { name: status_name }) }
|
|
17
|
+
scope :processing_list, -> { joins(:order_status).where(cartify_order_statuses: { name: %i[in_queue in_progress in_delivery] }) }
|
|
18
|
+
scope :processing_order, -> { where_status('in_queue').order('updated_at').last }
|
|
19
|
+
scope :in_progress, -> { where_status('in_progress').first.try(:id) }
|
|
20
|
+
|
|
21
|
+
def subtotal
|
|
22
|
+
order_items.sum(&:total_price)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def total
|
|
26
|
+
subtotal_item_total + shipping_price
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def subtotal_item_total
|
|
30
|
+
subtotal - discount
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def discount
|
|
34
|
+
coupon.try(:value) || 0.00
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shipping_price
|
|
38
|
+
delivery.try(:price) || 0.00
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def finalize
|
|
42
|
+
set_order_status :in_queue
|
|
43
|
+
save!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def set_order_status(status = :in_progress)
|
|
49
|
+
self.order_status_id = Cartify::OrderStatus.find_or_create_by(name: status).id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def update_subtotal
|
|
53
|
+
self[:subtotal] = subtotal
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update_total
|
|
57
|
+
self[:total] = total
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def connect_to_user
|
|
61
|
+
self[:user_id] = CurrentSession.user.id unless CurrentSession.user.nil?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class OrderItem < ApplicationRecord
|
|
3
|
+
belongs_to :product, class_name: Cartify.product_class.to_s
|
|
4
|
+
belongs_to :order
|
|
5
|
+
has_one :order_status, through: :order
|
|
6
|
+
has_one :category, through: :product # was :book
|
|
7
|
+
|
|
8
|
+
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
|
9
|
+
validate :product_present
|
|
10
|
+
validate :order_present
|
|
11
|
+
|
|
12
|
+
before_save :finalize
|
|
13
|
+
default_scope { order(product_id: :asc) }
|
|
14
|
+
|
|
15
|
+
def unit_price
|
|
16
|
+
return self[:unit_price] if persisted?
|
|
17
|
+
product.price
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def total_price
|
|
21
|
+
unit_price * quantity
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def product_present
|
|
27
|
+
errors.add(:product, 'is not valid or is not active.') if product.nil?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def order_present
|
|
31
|
+
errors.add(:order, 'is not a valid order.') if order.nil?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def finalize
|
|
35
|
+
self[:unit_price] = unit_price
|
|
36
|
+
self[:total_price] = quantity * self[:unit_price]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Cartify
|
|
2
|
+
class OrderStatus < ApplicationRecord
|
|
3
|
+
has_many :orders
|
|
4
|
+
|
|
5
|
+
validates :name, presence: true, uniqueness: true
|
|
6
|
+
validates_length_of :name, maximum: 30
|
|
7
|
+
|
|
8
|
+
scope :prohibited_to_change, -> { where(name: %i[in_progress in_queue]) }
|
|
9
|
+
|
|
10
|
+
def valid_step?(target)
|
|
11
|
+
if Cartify::OrderStatus.find(target).name == 'delivered'
|
|
12
|
+
name == 'in_delivery'
|
|
13
|
+
else
|
|
14
|
+
Cartify::OrderStatus.prohibited_to_change.ids.exclude? target.to_i
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
# filter for orders
|
|
3
|
+
module Cartify
|
|
4
|
+
class OrdersQuery
|
|
5
|
+
attr_reader :relation
|
|
6
|
+
|
|
7
|
+
def initialize(relation = Cartify::Order.none)
|
|
8
|
+
@relation = relation
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run(filter_params = '')
|
|
12
|
+
status_id = Cartify::OrderStatus.find_by(name: filter_params)
|
|
13
|
+
return relation.where(order_status_id: status_id) if status_id
|
|
14
|
+
relation
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
%main.container
|
|
2
|
+
%h1.general-title-margin= t('settings.settings')
|
|
3
|
+
%ul.nav.clearfix.mb-20{role: "tablist"}
|
|
4
|
+
%li.tab-item{role: "presentation"}
|
|
5
|
+
= link_to t('settings.address'), settings_addresses_path, class: 'tab-link filter-link active'
|
|
6
|
+
%li.tab-item{role: "presentation"}
|
|
7
|
+
= link_to t('settings.privacy'), 'settings_privacy_path', class: 'tab-link'
|
|
8
|
+
.tab-content
|
|
9
|
+
= render 'cartify/shared/addresses_form'
|