Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an orders/cart component #5441

Merged
merged 6 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions admin/app/components/solidus_admin/orders/cart/component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<div
class="w-full relative overflow-visible"
data-controller="<%= stimulus_id %>"
data-<%= stimulus_id %>-products-url-value="<%= solidus_admin.variants_for_order_path(@order) %>"
data-action="
keydown-><%= stimulus_id %>#selectResult
"
data-<%= stimulus_id %>-loading-text-value="<%= t('.loading') %>"
data-<%= stimulus_id %>-initial-text-value="<%= t('.initial') %>"
data-<%= stimulus_id %>-empty-text-value="<%= t('.empty') %>"
>
<%= render component('ui/panel').new do |panel| %>
<div>
<div class="peer">
<%= render component("ui/forms/search_field").new(
tvdeyen marked this conversation as resolved.
Show resolved Hide resolved
"data-action": "#{stimulus_id}#search focus->#{stimulus_id}#showResults #{stimulus_id}#showResults",
placeholder: t(".search_placeholder"),
"data-#{stimulus_id}-target": "searchField",
) %>
</div>

<%# results popover %>
<details class="px-6 relative overflow-visible">
<summary class="hidden"></summary>
<div
tvdeyen marked this conversation as resolved.
Show resolved Hide resolved
class="
absolute
left-0
top-2
bg-white
z-30
w-full
rounded-lg
shadow
border
border-gray-100
p-2
flex-col
gap-1
max-h-screen
overflow-y-auto
"
data-<%= stimulus_id %>-target="results"
>
</div>
</details>

</div>

<%# line items table %>
<div class="rounded-b-lg -mx-6 -mb-6 overflow-hidden">
<table class="table-auto w-full">
<thead>
<tr class="border-gray-100 border-t">
<th class="text-left body-small-bold text-gray-800 bg-gray-15 px-6 py-3 leading-none">Product</th>
<th class="text-left body-small-bold text-gray-800 bg-gray-15 px-6 py-3 leading-none w-16">Quantity</th>
<th class="text-left body-small-bold text-gray-800 bg-gray-15 px-6 py-3 leading-none w-16">Price</th>
<th class="text-left body-small-bold text-gray-800 bg-gray-15 px-6 py-3 leading-none w-16"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody>
<% @order.line_items.each do |line_item| %>
<tr class="border-gray-100 border-t">
<td class="px-6 py-4">
<div class="flex gap-2 grow">
<% variant = line_item.variant %>
<%= render component("ui/thumbnail").new(
src: (variant.images.first || variant.product.gallery.images.first)&.url(:small),
alt: variant.name
) %>
<div class="flex-col">
<div class="leading-5 text-black body-small-bold"><%= variant.name %></div>
<div class="leading-5 text-gray-500 body-small">
SKU: <%= variant.sku %>
<%= variant.options_text.presence&.prepend("- ") %>
</div>
</div>
</div>
</td>
<td class="px-6 py-4">
<%= form_for(line_item, url: solidus_admin.order_line_item_path(@order, line_item), html: {
"data-controller": "readonly-when-submitting"
}) do |f| %>
<%= render component("ui/forms/input").new(
name: "#{f.object_name}[quantity]",
type: :number,
value: line_item.quantity,
"aria-label": "Quantity",
min: 0,
class: "!w-16 inline-block",
"data-action": "input->#{stimulus_id}#updateLineItem",
) %>
<% render component("ui/button").new(type: :submit, text: "Update", class: "inline-block") %>
<% end %>
</td>
<td class="px-6 py-4">
<span class="text-gray-500 body-small"><%= line_item.single_money.to_html %></span>
</td>
<td class="px-6 py-4 text-right">
<%= form_for(line_item, url: solidus_admin.order_line_item_path(@order, line_item), method: :delete) do |f| %>
<%= render component('ui/button').new(
scheme: :ghost,
size: :s,
title: t("spree.delete"),
icon: 'close-line',
"data-controller": "confirm",
"data-confirm-text-value": t("spree.are_you_sure"),
) %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>

<% end %>
</div>
135 changes: 135 additions & 0 deletions admin/app/components/solidus_admin/orders/cart/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Controller } from "@hotwired/stimulus"
import { useClickOutside, useDebounce } from "stimulus-use"

const QUERY_KEY = "q[name_or_variants_including_master_sku_cont]"
tvdeyen marked this conversation as resolved.
Show resolved Hide resolved

export default class extends Controller {
static targets = ["result", "results", "searchField"]
static values = {
results: String,
productsUrl: String,
loadingText: String,
initialText: String,
emptyText: String,
}
static debounces = [
{
name: "requestSubmitForLineItems",
wait: 500,
},
"search",
]

get query() {
return this.searchFieldTarget.value
}

get selectedResult() {
// Keep the index within boundaries
if (this.selectedIndex < 0) this.selectedIndex = 0
if (this.selectedIndex >= this.resultTargets.length) this.selectedIndex = this.resultTargets.length - 1

return this.resultTargets[this.selectedIndex]
}

connect() {
useClickOutside(this)
useDebounce(this)

this.selectedIndex = 0
this.lineItemsToBeSubmitted = []

if (this.query) {
this.showResults()
this.search()
}
}

selectResult(event) {
switch (event.key) {
case "Enter":
event.preventDefault()
this.selectedResult?.click()
break
case "ArrowUp":
event.preventDefault()
this.selectedIndex -= 1
this.render()
break
case "ArrowDown":
event.preventDefault()
this.selectedIndex += 1
this.render()
break
}
}

clickOutside() {
this.openResults = false
this.render()
}

async search() {
const query = this.query

if (query) {
this.resultsValue = this.loadingTextValue
this.render()

this.resultsValue = (await (await fetch(`${this.productsUrlValue}?${QUERY_KEY}=${query}`)).text()) || this.emptyTextValue
this.render()
} else {
this.resultsValue = this.initialTextValue
this.render()
}
}

showResults() {
this.openResults = true
this.render()
}

updateLineItem(event) {
if (!this.lineItemsToBeSubmitted.includes(event.currentTarget)) {
this.lineItemsToBeSubmitted.push(event.currentTarget)
}

this.requestSubmitForLineItems()
}

// This is a workaround to permit using debounce when needing to pass a parameter
requestSubmitForLineItems() {
this.lineItemsToBeSubmitted.forEach((lineItem) => {
lineItem.form.requestSubmit()
})
this.lineItemsToBeSubmitted = []
}

render() {
let resultsHtml = this.resultsValue

if (this.renderedHtml !== resultsHtml) {
this.renderedHtml = resultsHtml
this.resultsTarget.innerHTML = resultsHtml
}

if (this.openResults && resultsHtml && this.query) {
if (!this.resultsTarget.parentNode.open) this.selectedIndex = 0

for (const result of this.resultTargets) {
if (result === this.selectedResult) {
if (!result.hasAttribute("aria-selected") && result.scrollIntoViewIfNeeded) {
// This is a non-standard method, but it's supported by all major browsers
result.scrollIntoViewIfNeeded()
}
result.setAttribute("aria-selected", true)
} else {
result.removeAttribute("aria-selected")
}
}
this.resultsTarget.parentNode.open = true
} else {
this.resultsTarget.parentNode.open = false
}
}
}
7 changes: 7 additions & 0 deletions admin/app/components/solidus_admin/orders/cart/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class SolidusAdmin::Orders::Cart::Component < SolidusAdmin::BaseComponent
def initialize(order:)
@order = order
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Add your component translations here.
# Use the translation in the example in your template with `t(".hello")`.
en:
search_placeholder: "Find a variant by name or SKU"
loading: "Loading..."
initial: "Type to search"
empty: "No results"
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<%= form_for(@order.line_items.build(variant: @variant), url: solidus_admin.order_line_items_path(@order), method: :post, html: {
"data-controller": "readonly-when-submitting #{stimulus_id}",
"data-action": "click->#{stimulus_id}#submit",
"data-#{component('orders/cart').stimulus_id}-target": "result",
class: "
rounded
p-2
items-center
flex
hover:bg-gray-25
aria-selected:bg-gray-25
cursor-pointer
"
}) do |f| %>
<%= hidden_field_tag("#{f.object_name}[variant_id]", @variant.id) %>
<div class="flex gap-2 grow">
<%= render component("ui/thumbnail").new(
src: @image&.url(:small),
alt: @variant.name
) %>
<div class="flex-col">
<div class="leading-5 text-black body-small-bold"><%= @variant.name %></div>
<div class="leading-5 text-gray-500 body-small">
SKU:
<%= @variant.sku %>
<%= @variant.options_text.presence&.prepend("- ") %>
</div>
</div>
</div>
<div class="flex gap-2 items-center">
<span class="text-gray-500 body-small"><%= render component("products/stock").from_variant(@variant) %></span>
<span class="text-black body-small"><%= @variant.display_price.to_html %></span>
</div>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
submit(event) {
event.currentTarget.requestSubmit()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class SolidusAdmin::Orders::Cart::Result::Component < SolidusAdmin::BaseComponent
with_collection_parameter :variant

def initialize(order:, variant:)
@order = order
@variant = variant
@image = @variant.images.first || @variant.product.gallery.images.first
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<%= render component("ui/button").new(
tag: :a,
text: t('.create_order'),
href: solidus_admin.new_order_path,
href: spree.new_admin_order_path,
icon: "add-line",
) %>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<%= page do %>
<%= page_header do %>
<%= page_header_back solidus_admin.orders_path %>

<%= page_header_title t(".create_order") %>

<%= page_header_back(solidus_admin.orders_path) %>
<%= page_header_title(t('.title', number: @order.number)) %>
<%= page_header_actions do %>
<%= render component("ui/button").new(tag: :button, scheme: :secondary, text: t(".discard"), form: form_id) %>
<%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %>
<% end %>
<% end %>

<%= render component('orders/cart').new(order: @order) %>
<% end %>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class SolidusAdmin::Orders::New::Component < SolidusAdmin::BaseComponent
class SolidusAdmin::Orders::Show::Component < SolidusAdmin::BaseComponent
include SolidusAdmin::Layout::PageHelpers

def initialize(order:)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Add your component translations here.
# Use the translation in the example in your template with `t(".hello")`.
en:
create_order: Create Order
save: Save
discard: Discard
title: "Order %{number}"
4 changes: 4 additions & 0 deletions admin/app/components/solidus_admin/ui/icon/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def initialize(name:, **attrs)

# Hide the icon from screen readers by default.
@attrs['aria-hidden'] = true unless @attrs.key?('aria-hidden')

# Default icons without style to 16x16, mostly useful in test snapshots.
@attrs['width'] = '16'
@attrs['height'] = '16'
end

def call
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div
class="
flex items-center justify-between
rounded inline-block px-3 py-2
rounded px-3 py-2
transform translate-y-full opacity-0 transition-all duration-500
<%= SCHEMES.fetch(@scheme.to_sym).join(' ') %>
"
Expand Down
2 changes: 1 addition & 1 deletion admin/app/components/solidus_admin/ui/toast/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ class SolidusAdmin::UI::Toast::Component < SolidusAdmin::BaseComponent
def initialize(text:, icon: nil, scheme: :default)
@text = text
@icon = icon
@scheme = scheme.to_sym
@scheme = scheme
end
end
Loading
Loading