Skip to content

Commit

Permalink
[wip] Include "Select address" feature
Browse files Browse the repository at this point in the history
  • Loading branch information
rainerdema committed Nov 14, 2023
1 parent 6bbb872 commit 56fe8ee
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
<div class="<%= stimulus_id %>">
<div class="<%= stimulus_id %>" data-controller="<%= stimulus_id %>">
<%= render component("orders/show").new(order: @order) %>
<%= render component("ui/modal").new(title: t(".title.#{@type}"), close_path: solidus_admin.order_path(@order)) do |modal| %>
<%= form_for @order, url: solidus_admin.send("order_#{@type}_address_path", @order), html: { id: form_id } do |form| %>
<div class="w-full flex flex-col mb-4">
<h2 class="text-sm mb-4 font-semibold"><%= t(".subtitle.#{@type}") %></h2>
<div class="flex justify-between items-center mb-4">
<h2 class="text-sm font-semibold"><%= t(".subtitle.#{@type}") %></h2>

<div class="flex justify-between items-center">
<div class="relative">
<button type="button" data-action="<%= stimulus_id %>#toggle" class="text-sm w-full text-left flex items-center justify-between">
<%= t(".select_address") %>
<%= render component("ui/icon").new(name: 'arrow-down-s-fill', class: 'w-5 h-5') %>
</button>

<div
data-<%= stimulus_id %>-target="menu"
class="absolute hidden right-0 my-1 bg-white border border-gray-300 rounded py-2 mt-1 shadow-lg z-10 w-full min-w-[16rem] max-h-[26rem] overflow-y-auto"
>
<% @order.user&.addresses&.each do |address| %>
<%= tag.a class: 'block text-sm hover:bg-gray-50 p-2 mx-2 w-auto rounded',
'data-turbo-frame': address_frame_id,
href: solidus_admin.send("order_#{@type}_address_path", @order, address_id: address.id) do %>
<%= format_address(address) %>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
<div class="w-full flex gap-4">
<%= form.fields_for :"#{@type}_address" do |address_form| %>
<%= render component('ui/forms/address').new(form: address_form, disabled: false) %>
<%= turbo_frame_tag address_frame_id, target: "_top" do %>
<%= render component('ui/forms/address').new(form: "order[#{@type}_address_attributes]", object: @address, disabled: false) %>
<% end %>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static targets = ["menu"]

toggle() {
this.menuTarget.classList.toggle('hidden')
}

close() {
this.menuTarget.classList.add('hidden')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ class SolidusAdmin::Orders::Show::Address::Component < SolidusAdmin::BaseCompone

VALID_TYPES = ['ship', 'bill'].freeze

def initialize(order:, type: 'ship')
def initialize(order:, address:, type: 'ship')
@order = order
@type = validate_address_type(type)
@address = address
end

def form_id
@form_id ||= "#{stimulus_id}--form-#{@type}-#{@order.id}"
end

def address_frame_id
@table_frame_id ||= "#{stimulus_id}--address-frame-#{@order.id}"
end

def use_attribute
case @type
when 'ship'
Expand All @@ -23,6 +28,24 @@ def use_attribute
end
end

def format_address(address)
return unless address
safe_join([
address.name,
tag.br,
address.address1,
tag.br,
address.address2,
address.city,
address.zipcode,
address.state&.name,
tag.br,
address.country.name,
tag.br,
address.phone,
], " ")
end

def validate_address_type(type)
VALID_TYPES.include?(type) ? type : raise(ArgumentError, "Invalid address type: #{type}")
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ en:
save: Save
cancel: Cancel
back: Back
select_address: Select address
title:
ship: Edit Shipping Address
bill: Edit Billing Address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<%= page_with_sidebar_aside do %>
<%= render component('ui/panel').new do |panel| %>
<% panel.with_menu t(".edit_email"), solidus_admin.order_customer_path(@order) %>
<% panel.with_menu t(".edit_shipping"), solidus_admin.new_order_ship_address_path(@order) %>
<% panel.with_menu t(".edit_billing"), solidus_admin.new_order_bill_address_path(@order) %>
<% panel.with_menu t(".edit_shipping"), solidus_admin.edit_order_ship_address_path(@order) %>
<% panel.with_menu t(".edit_billing"), solidus_admin.edit_order_bill_address_path(@order) %>
<% panel.with_menu t(".remove_customer"), solidus_admin.order_customer_path(@order), method: :delete, class: "text-red-500" if @order.user %>

<% panel.with_section(class: 'flex flex-col gap-6') do %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<fieldset class="<%= stimulus_id %>"
<fieldset class="<%= stimulus_id %> address-form-selector"
data-controller="<%= stimulus_id %>"
<%= :disabled if @disabled %>
>
<div class="<%= stimulus_id %>--address-form flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(@form, :name) %>
<%= render component("ui/forms/field").text_field(@form, :address1) %>
<%= render component("ui/forms/field").text_field(@form, :address2) %>
<%= render component("ui/forms/field").text_field(@form, :name, object: @object) %>
<%= render component("ui/forms/field").text_field(@form, :address1, object: @object) %>
<%= render component("ui/forms/field").text_field(@form, :address2, object: @object) %>
<div class="flex gap-4 w-full">
<%= render component("ui/forms/field").text_field(@form, :city) %>
<%= render component("ui/forms/field").text_field(@form, :zipcode) %>
<%= render component("ui/forms/field").text_field(@form, :city, object: @object) %>
<%= render component("ui/forms/field").text_field(@form, :zipcode, object: @object) %>
</div>

<%= render component("ui/forms/field").select(
@form,
:country_id,
Spree::Country.all.map { |c| [c.name, c.id] },
value: @form.object.try(:country_id),
object: @object,
value: @object.try(:country_id),
"data-#{stimulus_id}-target": "country",
"data-action": "change->#{stimulus_id}#loadStates"
) %>
Expand All @@ -24,11 +25,12 @@
@form,
:state_id,
state_options,
value: @form.object.try(:state_id),
disabled: @form.object.country&.states&.empty?,
object: @object,
value: @object.try(:state_id),
disabled: @object.country&.states&.empty?,
"data-#{stimulus_id}-target": "state"
) %>

<%= render component("ui/forms/field").text_field(@form, :phone) %>
<%= render component("ui/forms/field").text_field(@form, :phone, object: @object) %>
</div>
</fieldset>
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static targets = ["country", "state"]
static targets = ["state", "country"]

countryValueChanged() {
this.loadStates()
}

loadStates() {
const countryId = this.countryTarget.value
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# frozen_string_literal: true

class SolidusAdmin::UI::Forms::Address::Component < SolidusAdmin::BaseComponent
def initialize(form:, disabled: false)
def initialize(form:, object:, disabled: false, html_attributes: {})
@form = form
@object = object
@disabled = disabled
@html_attributes = html_attributes
end

def state_options
return [] unless @form.object.country
@form.object.country.states.map { |s| [s.name, s.id] }
return [] unless @object.country
@object.country.states.map { |s| [s.name, s.id] }
end
end
47 changes: 32 additions & 15 deletions admin/app/components/solidus_admin/ui/forms/field/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,78 @@ def initialize(label:, hint: nil, tip: nil, error: nil, input_attributes: nil, *
raise ArgumentError, "provide either a block or input_attributes" if content? && input_attributes
end

def self.text_field(form, method, hint: nil, tip: nil, size: :m, **attributes)
errors = form.object.errors.messages_for(method).presence
def self.text_field(form, method, object: nil, hint: nil, tip: nil, size: :m, **attributes)
object_name, object, label, errors = extract_form_details(form, object, method)

new(
label: form.object.class.human_attribute_name(method),
label: label,
hint: hint,
tip: tip,
error: errors,
input_attributes: {
name: "#{form.object_name}[#{method}]",
name: "#{object_name}[#{method}]",
tag: :input,
size: size,
value: form.object.public_send(method),
value: object.public_send(method),
error: (errors.to_sentence.capitalize if errors),
**attributes,
}
)
end

def self.select(form, method, choices, hint: nil, tip: nil, size: :m, **attributes)
errors = form.object.errors.messages_for(method).presence
def self.select(form, method, choices, object: nil, hint: nil, tip: nil, size: :m, **attributes)
object_name, object, label, errors = extract_form_details(form, object, method)

new(
label: form.object.class.human_attribute_name(method),
label: label,
hint: hint,
tip: tip,
error: errors,
input_attributes: {
name: "#{form.object_name}[#{method}]",
name: "#{object_name}[#{method}]",
tag: :select,
choices: choices,
size: size,
value: form.object.public_send(method),
value: object.public_send(method),
error: (errors.to_sentence.capitalize if errors),
**attributes,
}
)
end

def self.text_area(form, method, hint: nil, tip: nil, size: :m, **attributes)
errors = form.object.errors.messages_for(method).presence
def self.text_area(form, method, object: nil, hint: nil, tip: nil, size: :m, **attributes)
object_name, object, label, errors = extract_form_details(form, object, method)

new(
label: form.object.class.human_attribute_name(method),
label: label,
hint: hint,
tip: tip,
error: errors,
input_attributes: {
name: "#{form.object_name}[#{method}]",
name: "#{object_name}[#{method}]",
size: size,
tag: :textarea,
value: form.object.public_send(method),
value: object.public_send(method),
error: (errors.to_sentence.capitalize if errors),
**attributes,
}
)
end

def self.extract_form_details(form, object, method)
if form.is_a?(String)
object_name = form
raise ArgumentError, "Object must be provided when form name is a string" unless object
elsif form.respond_to?(:object)
object_name = form.object_name
object = form.object
else
raise ArgumentError, "Invalid arguments: expected a form object or form.object_name and form.object"
end

errors = object.errors.messages_for(method).presence if object.respond_to?(:errors)
label = object.class.human_attribute_name(method)

[object_name, object, label, errors]
end
end
43 changes: 36 additions & 7 deletions admin/app/controllers/solidus_admin/addresses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,60 @@ class AddressesController < BaseController
before_action :load_order
before_action :validate_address_type

def new
address = @order.send("#{address_type}_address")
@order.send("build_#{address_type}_address", country_id: default_country_id) if address.nil?
address ||= @order.send("#{address_type}_address")
address.country_id ||= default_country_id if address.country.nil?
def show
address = find_address || build_new_address

respond_to do |format|
format.html { render component('orders/show/address').new(order: @order, type: address_type) }
format.html do
render component('orders/show/address').new(
order: @order,
address: address,
type: address_type,
)
end
end
end

def edit
redirect_to action: :show
end

def update
if @order.contents.update_cart(order_params)
redirect_to order_path(@order), status: :see_other, notice: t('.success')
else
flash.now[:error] = @order.errors[:base].join(", ") if @order.errors[:base].any?

respond_to do |format|
format.html { render component('orders/show/address').new(order: @order, type: address_type), status: :unprocessable_entity }
format.html do
render component('orders/show/address').new(
order: @order,
address: @order.send("#{address_type}_address"),
type: address_type,
status: :unprocessable_entity,
)
end
end
end
end

private

def find_address
if params[:address_id].present? && @order.user
#@order.user.addresses.find_by(id: params[:address_id])
@order.user.addresses.find(params[:address_id])
else
@order.send("#{address_type}_address")
end
end

def build_new_address
@order.send("build_#{address_type}_address", country_id: default_country_id).tap do |address|
address.country_id ||= default_country_id if address.country.nil?
end
end

def address_type
params[:type].presence_in(%w[bill ship])
end
Expand Down
4 changes: 2 additions & 2 deletions admin/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
resources :orders, only: [:index, :show, :edit, :update] do
resources :line_items, only: [:destroy, :create, :update]
resource :customer
resource :ship_address, only: [:new, :update], controller: "addresses", type: "ship"
resource :bill_address, only: [:new, :update], controller: "addresses", type: "bill"
resource :ship_address, only: [:show, :edit, :update], controller: "addresses", type: "ship"
resource :bill_address, only: [:show, :edit, :update], controller: "addresses", type: "bill"

member do
get :variants_for
Expand Down

0 comments on commit 56fe8ee

Please sign in to comment.