solidus_admin 0.0.0 → 0.0.1
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 +5 -5
- data/LICENSE +7 -0
- data/README.md +31 -0
- data/Rakefile +21 -0
- data/app/assets/config/solidus_admin_manifest.js +4 -0
- data/app/assets/images/solidus_admin/.keep +0 -0
- data/app/assets/images/solidus_admin/arrow_down_s_fill_gray_700.svg +3 -0
- data/app/assets/images/solidus_admin/arrow_down_s_fill_red_400.svg +3 -0
- data/app/assets/images/solidus_admin/arrow_right_up_line.svg +5 -0
- data/app/assets/images/solidus_admin/favicon.ico +0 -0
- data/app/assets/images/solidus_admin/remixicon.symbol.svg +11 -0
- data/app/assets/stylesheets/solidus_admin/application.css +3 -0
- data/app/assets/stylesheets/solidus_admin/application.tailwind.css.erb +35 -0
- data/app/components/solidus_admin/base_component.rb +42 -0
- data/app/components/solidus_admin/feedback/component.html.erb +11 -0
- data/app/components/solidus_admin/feedback/component.rb +4 -0
- data/app/components/solidus_admin/feedback/component.yml +5 -0
- data/app/components/solidus_admin/orders/index/component.html.erb +31 -0
- data/app/components/solidus_admin/orders/index/component.rb +118 -0
- data/app/components/solidus_admin/orders/index/component.yml +13 -0
- data/app/components/solidus_admin/products/index/component.html.erb +30 -0
- data/app/components/solidus_admin/products/index/component.rb +126 -0
- data/app/components/solidus_admin/products/index/component.yml +13 -0
- data/app/components/solidus_admin/products/show/component.html.erb +149 -0
- data/app/components/solidus_admin/products/show/component.js +9 -0
- data/app/components/solidus_admin/products/show/component.rb +26 -0
- data/app/components/solidus_admin/products/show/component.yml +17 -0
- data/app/components/solidus_admin/products/status/component.rb +31 -0
- data/app/components/solidus_admin/products/status/component.yml +3 -0
- data/app/components/solidus_admin/sidebar/account_nav/component.html.erb +67 -0
- data/app/components/solidus_admin/sidebar/account_nav/component.rb +15 -0
- data/app/components/solidus_admin/sidebar/account_nav/component.yml +3 -0
- data/app/components/solidus_admin/sidebar/component.html.erb +39 -0
- data/app/components/solidus_admin/sidebar/component.js +14 -0
- data/app/components/solidus_admin/sidebar/component.rb +21 -0
- data/app/components/solidus_admin/sidebar/component.yml +2 -0
- data/app/components/solidus_admin/sidebar/item/component.html.erb +26 -0
- data/app/components/solidus_admin/sidebar/item/component.rb +27 -0
- data/app/components/solidus_admin/skip_link/component.rb +24 -0
- data/app/components/solidus_admin/skip_link/component.yml +2 -0
- data/app/components/solidus_admin/ui/badge/component.rb +34 -0
- data/app/components/solidus_admin/ui/button/component.rb +101 -0
- data/app/components/solidus_admin/ui/forms/checkbox/component.rb +42 -0
- data/app/components/solidus_admin/ui/forms/field/component.html.erb +28 -0
- data/app/components/solidus_admin/ui/forms/field/component.rb +72 -0
- data/app/components/solidus_admin/ui/forms/input/component.js +16 -0
- data/app/components/solidus_admin/ui/forms/input/component.rb +99 -0
- data/app/components/solidus_admin/ui/forms/switch/component.rb +47 -0
- data/app/components/solidus_admin/ui/icon/component.rb +25 -0
- data/app/components/solidus_admin/ui/icon/names.txt +2494 -0
- data/app/components/solidus_admin/ui/panel/component.html.erb +36 -0
- data/app/components/solidus_admin/ui/panel/component.js +14 -0
- data/app/components/solidus_admin/ui/panel/component.rb +19 -0
- data/app/components/solidus_admin/ui/panel/component.yml +4 -0
- data/app/components/solidus_admin/ui/tab/component.rb +43 -0
- data/app/components/solidus_admin/ui/table/component.html.erb +170 -0
- data/app/components/solidus_admin/ui/table/component.js +118 -0
- data/app/components/solidus_admin/ui/table/component.rb +150 -0
- data/app/components/solidus_admin/ui/table/component.yml +11 -0
- data/app/components/solidus_admin/ui/table/pagination/component.html.erb +28 -0
- data/app/components/solidus_admin/ui/table/pagination/component.rb +14 -0
- data/app/components/solidus_admin/ui/table/pagination/component.yml +3 -0
- data/app/components/solidus_admin/ui/toast/component.html.erb +26 -0
- data/app/components/solidus_admin/ui/toast/component.js +17 -0
- data/app/components/solidus_admin/ui/toast/component.rb +18 -0
- data/app/components/solidus_admin/ui/toast/component.yml +4 -0
- data/app/components/solidus_admin/ui/toggletip/component.html.erb +53 -0
- data/app/components/solidus_admin/ui/toggletip/component.js +26 -0
- data/app/components/solidus_admin/ui/toggletip/component.rb +98 -0
- data/app/components/solidus_admin/ui/toggletip/component.yml +2 -0
- data/app/controllers/solidus_admin/accounts_controller.rb +11 -0
- data/app/controllers/solidus_admin/authentication_adapters/backend.rb +26 -0
- data/app/controllers/solidus_admin/base_controller.rb +21 -0
- data/app/controllers/solidus_admin/controller_helpers/authentication.rb +31 -0
- data/app/controllers/solidus_admin/controller_helpers/authorization.rb +29 -0
- data/app/controllers/solidus_admin/controller_helpers/locale.rb +32 -0
- data/app/controllers/solidus_admin/orders_controller.rb +21 -0
- data/app/controllers/solidus_admin/products_controller.rb +93 -0
- data/app/helpers/solidus_admin/components_helper.rb +9 -0
- data/app/helpers/solidus_admin/layout_helper.rb +18 -0
- data/app/javascript/solidus_admin/application.js +2 -0
- data/app/javascript/solidus_admin/controllers/application.js +9 -0
- data/app/javascript/solidus_admin/controllers/components.js +35 -0
- data/app/javascript/solidus_admin/controllers/hello_controller.js +7 -0
- data/app/javascript/solidus_admin/controllers/index.js +14 -0
- data/app/javascript/solidus_admin/utils.js +8 -0
- data/app/views/layouts/solidus_admin/application.html.erb +30 -0
- data/app/views/layouts/solidus_admin/preview.html.erb +10 -0
- data/app/views/solidus_admin/.keep +0 -0
- data/bin/rails +13 -0
- data/config/importmap.rb +13 -0
- data/config/locales/main_nav.en.yml +13 -0
- data/config/locales/orders.en.yml +4 -0
- data/config/locales/products.en.yml +10 -0
- data/config/routes.rb +13 -0
- data/config/solidus_admin/tailwind.config.js.erb +95 -0
- data/docs/customizing_main_navigation.md +42 -0
- data/docs/customizing_tailwind.md +78 -0
- data/docs/customizing_view_components.md +153 -0
- data/lib/generators/solidus_admin/component/USAGE +13 -0
- data/lib/generators/solidus_admin/component/component_generator.rb +130 -0
- data/lib/generators/solidus_admin/component/templates/component.html.erb.tt +3 -0
- data/lib/generators/solidus_admin/component/templates/component.js.tt +14 -0
- data/lib/generators/solidus_admin/component/templates/component.rb.tt +14 -0
- data/lib/generators/solidus_admin/component/templates/component.yml.tt +4 -0
- data/lib/generators/solidus_admin/component/templates/component_preview.rb.tt +15 -0
- data/lib/generators/solidus_admin/component/templates/component_preview_overview.html.erb +7 -0
- data/lib/generators/solidus_admin/component/templates/component_spec.rb.tt +16 -0
- data/lib/generators/solidus_admin/install/install_generator.rb +44 -0
- data/lib/generators/solidus_admin/install/templates/config/initializers/solidus_admin.rb +44 -0
- data/lib/solidus_admin/configuration.rb +217 -0
- data/lib/solidus_admin/engine.rb +67 -0
- data/lib/solidus_admin/importmap.rb +26 -0
- data/lib/solidus_admin/main_nav_item.rb +97 -0
- data/lib/solidus_admin/preview.rb +81 -0
- data/lib/solidus_admin/tailwindcss.rb +58 -0
- data/lib/solidus_admin/version.rb +5 -0
- data/lib/solidus_admin.rb +15 -0
- data/lib/tasks/importmap.rake +10 -0
- data/lib/tasks/tailwindcss.rake +55 -0
- data/solidus_admin.gemspec +35 -0
- metadata +255 -18
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
.body-tiny {
|
|
7
|
+
@apply font-sans font-normal text-xs leading-5;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.body-tiny-bold {
|
|
11
|
+
@apply font-sans font-semibold text-xs leading-5;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.body-small {
|
|
15
|
+
@apply font-sans font-normal text-sm leading-6;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.body-small-bold {
|
|
19
|
+
@apply font-sans font-semibold text-sm leading-6;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.body-text {
|
|
23
|
+
@apply font-sans font-normal text-base leading-6;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.body-text-bold {
|
|
27
|
+
@apply font-sans font-semibold text-base leading-6;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.body-title {
|
|
31
|
+
@apply font-sans font-semibold text-xl leading-5;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
<%= SolidusAdmin::Config.tailwind_stylesheets.map { File.read(_1) }.join("\n") %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusAdmin
|
|
4
|
+
# BaseComponent is the base class for all components in Solidus Admin.
|
|
5
|
+
class BaseComponent < ViewComponent::Base
|
|
6
|
+
include SolidusAdmin::ComponentsHelper
|
|
7
|
+
include Turbo::FramesHelper
|
|
8
|
+
|
|
9
|
+
def icon_tag(name, **attrs)
|
|
10
|
+
render component("ui/icon").new(name: name, **attrs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def missing_translation(key, options)
|
|
14
|
+
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
|
|
15
|
+
|
|
16
|
+
logger.debug " [#{self.class}] Missing translation: #{keys.join('.')}"
|
|
17
|
+
|
|
18
|
+
if options[:locale] != :en
|
|
19
|
+
t(key, **options, locale: :en)
|
|
20
|
+
else
|
|
21
|
+
"translation missing: #{keys.join('.')}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.stimulus_id
|
|
26
|
+
@stimulus_id ||= name.underscore
|
|
27
|
+
.sub(/^solidus_admin\/(.*)\/component$/, '\1')
|
|
28
|
+
.gsub("/", "--")
|
|
29
|
+
.tr("_", "-")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
delegate :stimulus_id, to: :class
|
|
33
|
+
|
|
34
|
+
def spree
|
|
35
|
+
@spree ||= Spree::Core::Engine.routes.url_helpers
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def solidus_admin
|
|
39
|
+
@solidus_admin ||= SolidusAdmin::Engine.routes.url_helpers
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<span class="max-w-xs text-xs text-right text-gray-600 hidden lg:block">
|
|
2
|
+
<%= t(".feedback_description") %>
|
|
3
|
+
</span>
|
|
4
|
+
<%= render component("ui/button").new(
|
|
5
|
+
tag: :a,
|
|
6
|
+
text: t(".give_feedback"),
|
|
7
|
+
href: "https://solidus.io/feedback?category=solidus-admin",
|
|
8
|
+
icon: "feedback-line",
|
|
9
|
+
scheme: :secondary,
|
|
10
|
+
target: :_blank,
|
|
11
|
+
) %>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Add your component translations here.
|
|
2
|
+
# Use the translation in the example in your template with `t(".hello")`.
|
|
3
|
+
en:
|
|
4
|
+
give_feedback: 'Give feedback'
|
|
5
|
+
feedback_description: 'We are constantly trying to improve. Please let us know what you think about this admin page.'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<div class="<%= stimulus_id %> px-4">
|
|
2
|
+
<header class="py-6 flex items-center">
|
|
3
|
+
<h1 class="body-title">
|
|
4
|
+
<%= title %>
|
|
5
|
+
</h1>
|
|
6
|
+
|
|
7
|
+
<div class="ml-auto flex gap-2 items-center">
|
|
8
|
+
<%= render component("feedback").new %>
|
|
9
|
+
<%= render component("ui/button").new(
|
|
10
|
+
tag: :a,
|
|
11
|
+
text: t('.create_order'),
|
|
12
|
+
href: spree.new_admin_order_path,
|
|
13
|
+
icon: "add-line",
|
|
14
|
+
) %>
|
|
15
|
+
</div>
|
|
16
|
+
</header>
|
|
17
|
+
|
|
18
|
+
<%= render component('ui/table').new(
|
|
19
|
+
id: 'orders-list',
|
|
20
|
+
model_class: Spree::Order,
|
|
21
|
+
rows: @page.records,
|
|
22
|
+
fade_row_proc: fade_row_proc,
|
|
23
|
+
search_key: SolidusAdmin::Config[:order_search_key],
|
|
24
|
+
search_url: solidus_admin.orders_path,
|
|
25
|
+
batch_actions: batch_actions,
|
|
26
|
+
filters: filters,
|
|
27
|
+
columns: columns,
|
|
28
|
+
prev_page_link: prev_page_link,
|
|
29
|
+
next_page_link: next_page_link,
|
|
30
|
+
) %>
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::Orders::Index::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
def initialize(page:)
|
|
5
|
+
@page = page
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class_attribute :fade_row_proc, default: ->(order) { order.paid? && order.shipped? }
|
|
9
|
+
|
|
10
|
+
def title
|
|
11
|
+
Spree::Order.model_name.human.pluralize
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def prev_page_link
|
|
15
|
+
@page.first? ? nil : solidus_admin.url_for(host: request.host, port: request.port, **request.params, page: @page.number - 1)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def next_page_link
|
|
19
|
+
@page.last? ? nil : solidus_admin.url_for(host: request.host, port: request.port, **request.params, page: @page.next_param)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def batch_actions
|
|
23
|
+
[]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def filters
|
|
27
|
+
[
|
|
28
|
+
{
|
|
29
|
+
name: 'q[completed_at_not_null]',
|
|
30
|
+
value: 1,
|
|
31
|
+
label: t('.filters.only_show_complete_orders'),
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def columns
|
|
37
|
+
[
|
|
38
|
+
number_column,
|
|
39
|
+
date_column,
|
|
40
|
+
customer_column,
|
|
41
|
+
total_column,
|
|
42
|
+
items_column,
|
|
43
|
+
payment_column,
|
|
44
|
+
shipment_column,
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def number_column
|
|
49
|
+
{
|
|
50
|
+
header: :order,
|
|
51
|
+
data: ->(order) do
|
|
52
|
+
order_path = spree.edit_admin_order_path(order)
|
|
53
|
+
|
|
54
|
+
if !fade_row_proc.call(order)
|
|
55
|
+
link_to order.number, order_path, class: 'font-semibold'
|
|
56
|
+
else
|
|
57
|
+
link_to order.number, order_path
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def date_column
|
|
64
|
+
{
|
|
65
|
+
header: :date,
|
|
66
|
+
data: ->(order) do
|
|
67
|
+
content_tag :div, l(order.created_at, format: :short)
|
|
68
|
+
end
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def customer_column
|
|
73
|
+
{
|
|
74
|
+
class_name: "w-[400px]",
|
|
75
|
+
header: :customer,
|
|
76
|
+
data: ->(order) do
|
|
77
|
+
customer_email = order.user&.email
|
|
78
|
+
content_tag :div, String(customer_email)
|
|
79
|
+
end
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def total_column
|
|
84
|
+
{
|
|
85
|
+
header: :total,
|
|
86
|
+
data: ->(order) do
|
|
87
|
+
content_tag :div, number_to_currency(order.total)
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def items_column
|
|
93
|
+
{
|
|
94
|
+
header: :items,
|
|
95
|
+
data: ->(order) do
|
|
96
|
+
content_tag :div, t('.columns.items', count: order.line_items.sum(:quantity))
|
|
97
|
+
end
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def payment_column
|
|
102
|
+
{
|
|
103
|
+
header: :payment,
|
|
104
|
+
data: ->(order) do
|
|
105
|
+
component('ui/badge').new(name: order.payment_state&.humanize, color: order.paid? ? :green : :yellow)
|
|
106
|
+
end
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def shipment_column
|
|
111
|
+
{
|
|
112
|
+
header: :shipment,
|
|
113
|
+
data: ->(order) do
|
|
114
|
+
component('ui/badge').new(name: order.shipment_state&.humanize, color: order.shipped? ? :green : :yellow)
|
|
115
|
+
end
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Add your component translations here.
|
|
2
|
+
# Use the translation in the example in your template with `t(".hello")`.
|
|
3
|
+
en:
|
|
4
|
+
create_order: 'Create Order'
|
|
5
|
+
columns:
|
|
6
|
+
items:
|
|
7
|
+
one: 1 Item
|
|
8
|
+
other: '%{count} Items'
|
|
9
|
+
filters:
|
|
10
|
+
only_show_complete_orders: Only show complete orders
|
|
11
|
+
date:
|
|
12
|
+
formats:
|
|
13
|
+
short: '%d %b %y'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div class="<%= stimulus_id %> px-4">
|
|
2
|
+
<header class="py-6 flex items-center">
|
|
3
|
+
<h1 class="body-title">
|
|
4
|
+
<%= title %>
|
|
5
|
+
</h1>
|
|
6
|
+
|
|
7
|
+
<div class="ml-auto flex gap-2 items-center">
|
|
8
|
+
<%= render component("feedback").new %>
|
|
9
|
+
<%= render component("ui/button").new(
|
|
10
|
+
tag: :a,
|
|
11
|
+
text: t('.add_product'),
|
|
12
|
+
href: spree.new_admin_product_path,
|
|
13
|
+
icon: "add-line",
|
|
14
|
+
) %>
|
|
15
|
+
</div>
|
|
16
|
+
</header>
|
|
17
|
+
|
|
18
|
+
<%= render component('ui/table').new(
|
|
19
|
+
id: 'products-list',
|
|
20
|
+
model_class: Spree::Product,
|
|
21
|
+
rows: @page.records,
|
|
22
|
+
search_key: SolidusAdmin::Config[:product_search_key],
|
|
23
|
+
search_url: solidus_admin.products_path,
|
|
24
|
+
batch_actions: batch_actions,
|
|
25
|
+
filters: filters,
|
|
26
|
+
columns: columns,
|
|
27
|
+
prev_page_link: prev_page_link,
|
|
28
|
+
next_page_link: next_page_link,
|
|
29
|
+
) %>
|
|
30
|
+
</div>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::Products::Index::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
def initialize(page:)
|
|
5
|
+
@page = page
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def title
|
|
9
|
+
Spree::Product.model_name.human.pluralize
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def prev_page_link
|
|
13
|
+
@page.first? ? nil : solidus_admin.url_for(host: request.host, port: request.port, **request.params, page: @page.number - 1)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def next_page_link
|
|
17
|
+
@page.last? ? nil : solidus_admin.url_for(host: request.host, port: request.port, **request.params, page: @page.next_param)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def batch_actions
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
display_name: t('.batch_actions.delete'),
|
|
24
|
+
action: solidus_admin.products_path,
|
|
25
|
+
method: :delete,
|
|
26
|
+
icon: 'delete-bin-7-line',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
display_name: t('.batch_actions.discontinue'),
|
|
30
|
+
action: solidus_admin.discontinue_products_path,
|
|
31
|
+
method: :put,
|
|
32
|
+
icon: 'pause-circle-line',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
display_name: t('.batch_actions.activate'),
|
|
36
|
+
action: solidus_admin.activate_products_path,
|
|
37
|
+
method: :put,
|
|
38
|
+
icon: 'play-circle-line',
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def filters
|
|
44
|
+
[
|
|
45
|
+
{
|
|
46
|
+
name: 'q[with_discarded]',
|
|
47
|
+
value: true,
|
|
48
|
+
label: t('.filters.with_deleted'),
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def columns
|
|
54
|
+
[
|
|
55
|
+
image_column,
|
|
56
|
+
name_column,
|
|
57
|
+
status_column,
|
|
58
|
+
price_column,
|
|
59
|
+
stock_column,
|
|
60
|
+
]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def image_column
|
|
64
|
+
{
|
|
65
|
+
class_name: "w-[72px]",
|
|
66
|
+
header: tag.span('aria-label': t('.product_image'), role: 'text'),
|
|
67
|
+
data: ->(product) do
|
|
68
|
+
image = product.gallery.images.first or return
|
|
69
|
+
|
|
70
|
+
link_to(
|
|
71
|
+
image_tag(image.url(:small), class: 'h-10 w-10 max-w-min rounded border border-gray-100', alt: product.name),
|
|
72
|
+
solidus_admin.product_path(product),
|
|
73
|
+
class: 'inline-flex overflow-hidden',
|
|
74
|
+
tabindex: -1,
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def name_column
|
|
81
|
+
{
|
|
82
|
+
header: :name,
|
|
83
|
+
data: ->(product) do
|
|
84
|
+
link_to product.name, solidus_admin.product_path(product)
|
|
85
|
+
end
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def status_column
|
|
90
|
+
{
|
|
91
|
+
header: :status,
|
|
92
|
+
data: ->(product) { component('products/status').new(product: product) }
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def stock_column
|
|
97
|
+
{
|
|
98
|
+
header: :stock,
|
|
99
|
+
data: ->(product) do
|
|
100
|
+
stock_info =
|
|
101
|
+
case (on_hand = product.total_on_hand)
|
|
102
|
+
when Float::INFINITY
|
|
103
|
+
content_tag :span, t('.stock.in_stock', on_hand: t('.stock.infinity')), class: 'text-forest'
|
|
104
|
+
when 1..Float::INFINITY
|
|
105
|
+
content_tag :span, t('.stock.in_stock', on_hand: on_hand), class: 'text-forest'
|
|
106
|
+
else
|
|
107
|
+
content_tag :span, t('.stock.in_stock', on_hand: on_hand), class: 'text-red-500'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
variant_info =
|
|
111
|
+
t('.for_variants', count: product.variants.count)
|
|
112
|
+
|
|
113
|
+
content_tag :div, safe_join([stock_info, variant_info], ' ')
|
|
114
|
+
end
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def price_column
|
|
119
|
+
{
|
|
120
|
+
header: :price,
|
|
121
|
+
data: ->(product) do
|
|
122
|
+
content_tag :div, product.master.display_price.to_html
|
|
123
|
+
end
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
en:
|
|
2
|
+
product_image: 'Image'
|
|
3
|
+
add_product: 'Add Product'
|
|
4
|
+
stock:
|
|
5
|
+
infinity: '∞'
|
|
6
|
+
in_stock: '%{on_hand} in stock'
|
|
7
|
+
for_variants: 'for %{count} variants'
|
|
8
|
+
batch_actions:
|
|
9
|
+
delete: 'Delete'
|
|
10
|
+
discontinue: 'Discontinue'
|
|
11
|
+
activate: 'Activate'
|
|
12
|
+
filters:
|
|
13
|
+
with_deleted: Include deleted
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<div class="px-4 relative" data-controller="<%= stimulus_id %>">
|
|
2
|
+
<header class="py-6 flex items-center gap-4">
|
|
3
|
+
<%= render component("ui/button").new(
|
|
4
|
+
tag: :a,
|
|
5
|
+
title: t(".back"),
|
|
6
|
+
icon: "arrow-left-line",
|
|
7
|
+
scheme: :secondary,
|
|
8
|
+
href: solidus_admin.products_path
|
|
9
|
+
) %>
|
|
10
|
+
<h1 class="flex items-center gap-2">
|
|
11
|
+
<span class="body-title"><%= @product.name %></span>
|
|
12
|
+
<%= render component("products/status").new(product: @product) %>
|
|
13
|
+
</h1>
|
|
14
|
+
|
|
15
|
+
<div class="ml-auto flex gap-2 items-center">
|
|
16
|
+
<%= render component("feedback").new %>
|
|
17
|
+
<%= render component("ui/button").new(
|
|
18
|
+
tag: :a,
|
|
19
|
+
text: t(".duplicate"),
|
|
20
|
+
href: spree.clone_admin_product_path(@product),
|
|
21
|
+
scheme: :ghost
|
|
22
|
+
) %>
|
|
23
|
+
|
|
24
|
+
<%= render component("ui/button").new(
|
|
25
|
+
tag: :a,
|
|
26
|
+
text: t(".view"),
|
|
27
|
+
href: SolidusAdmin::Config.storefront_product_path(@product),
|
|
28
|
+
target: :_blank,
|
|
29
|
+
scheme: :ghost
|
|
30
|
+
) %>
|
|
31
|
+
<%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %>
|
|
32
|
+
</div>
|
|
33
|
+
</header>
|
|
34
|
+
|
|
35
|
+
<%= form_for @product, url: solidus_admin.product_path(@product), html: { id: form_id } do |f| %>
|
|
36
|
+
<div class="flex gap-4 items-start pb-4">
|
|
37
|
+
<div class="justify-center items-start gap-4 flex flex-col w-full">
|
|
38
|
+
<%= render component('ui/panel').new do %>
|
|
39
|
+
<%= render component("ui/forms/field").text_field(f, :name) %>
|
|
40
|
+
<%= render component("ui/forms/field").text_field(f, :slug) %>
|
|
41
|
+
<%= render component("ui/forms/field").text_area(f, :description) %>
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
44
|
+
<%= render component('ui/panel').new(title: 'SEO', title_hint: 'Search Engine Optimization') do %>
|
|
45
|
+
<%= render component("ui/forms/field").text_field(f, :meta_title) %>
|
|
46
|
+
<%= render component("ui/forms/field").text_field(f, :meta_description) %>
|
|
47
|
+
<%= render component("ui/forms/field").text_area(f, :meta_keywords) %>
|
|
48
|
+
<% end %>
|
|
49
|
+
|
|
50
|
+
<%= render component('ui/panel').new(title: "Media") do |panel| %>
|
|
51
|
+
<% panel.with_action(
|
|
52
|
+
name: t(".manage_images"),
|
|
53
|
+
href: spree.admin_product_images_path(@product)
|
|
54
|
+
) %>
|
|
55
|
+
<% end %>
|
|
56
|
+
|
|
57
|
+
<%= render component('ui/panel').new(title: 'Pricing') do %>
|
|
58
|
+
<%= render component("ui/forms/field").text_field(f, :price) %>
|
|
59
|
+
<div class="flex gap-4 justify-items-stretch">
|
|
60
|
+
<%= render component("ui/forms/field").text_field(f, :cost_price) %>
|
|
61
|
+
<%= render component("ui/forms/field").text_field(f, :cost_currency) %>
|
|
62
|
+
</div>
|
|
63
|
+
<% end %>
|
|
64
|
+
|
|
65
|
+
<%= render component('ui/panel').new(title: 'Stock') do |panel| %>
|
|
66
|
+
<%= render component("ui/forms/field").text_field(f, :sku) %>
|
|
67
|
+
|
|
68
|
+
<% panel.with_action(
|
|
69
|
+
name: t(".manage_stock"),
|
|
70
|
+
href: spree.admin_product_stock_path(@product)
|
|
71
|
+
) %>
|
|
72
|
+
<% end %>
|
|
73
|
+
|
|
74
|
+
<%= render component('ui/panel').new(title: 'Shipping') do %>
|
|
75
|
+
<%= render component("ui/forms/field").select(
|
|
76
|
+
f,
|
|
77
|
+
:shipping_category_id,
|
|
78
|
+
[[t(".none"), nil]] + Spree::ShippingCategory.order(:name).pluck(:name, :id),
|
|
79
|
+
tip: t(".hints.shipping_category_html"),
|
|
80
|
+
) %>
|
|
81
|
+
<%= render component("ui/forms/field").select(
|
|
82
|
+
f,
|
|
83
|
+
:tax_category_id,
|
|
84
|
+
[[t(".none"), nil]] + Spree::TaxCategory.order(:name).pluck(:name, :id),
|
|
85
|
+
tip: t(
|
|
86
|
+
".hints.tax_category_html",
|
|
87
|
+
default_tax_category: Spree::TaxCategory.default&.name
|
|
88
|
+
),
|
|
89
|
+
) %>
|
|
90
|
+
<% end %>
|
|
91
|
+
|
|
92
|
+
<%= render component('ui/panel').new(title: "Options") do %>
|
|
93
|
+
<%= render component("ui/forms/field").select(
|
|
94
|
+
f,
|
|
95
|
+
:option_type_ids,
|
|
96
|
+
option_type_options,
|
|
97
|
+
multiple: true,
|
|
98
|
+
"size" => option_type_options.size,
|
|
99
|
+
) %>
|
|
100
|
+
<% end %>
|
|
101
|
+
|
|
102
|
+
<%= render component('ui/panel').new(title: "Specifications") do |panel| %>
|
|
103
|
+
<% panel.with_action(
|
|
104
|
+
name: t(".manage_properties"),
|
|
105
|
+
href: spree.admin_product_product_properties_path(@product)
|
|
106
|
+
) %>
|
|
107
|
+
<% end %>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<aside class="justify-center items-start gap-4 flex flex-col w-full max-w-sm">
|
|
111
|
+
<%= render component('ui/panel').new(title: "Publishing") do %>
|
|
112
|
+
<%= render component("ui/forms/field").text_field(f, :available_on, tip: t(".hints.available_on_html"), type: :date) %>
|
|
113
|
+
<%= render component("ui/forms/field").text_field(f, :discontinue_on, tip: t(".hints.discontinue_on_html"), type: :date) %>
|
|
114
|
+
|
|
115
|
+
<label class="flex gap-2 items-center">
|
|
116
|
+
<%= render component("ui/forms/checkbox").new(
|
|
117
|
+
name: "#{f.object_name}[promotionable]",
|
|
118
|
+
checked: f.object.promotionable
|
|
119
|
+
) %>
|
|
120
|
+
<span class="body-text-sm"><%= Spree::Product.human_attribute_name :promotionable %></span>
|
|
121
|
+
<%= render component("ui/toggletip").new(
|
|
122
|
+
text: t(".hints.promotionable_html"),
|
|
123
|
+
position: :left
|
|
124
|
+
) %>
|
|
125
|
+
</label>
|
|
126
|
+
<% end %>
|
|
127
|
+
|
|
128
|
+
<%= render component('ui/panel').new(title: "Product organization") do %>
|
|
129
|
+
<%= render component("ui/forms/field").select(f, :taxon_ids, taxon_options, multiple: true, "size" => taxon_options.size) %>
|
|
130
|
+
<% end %>
|
|
131
|
+
</aside>
|
|
132
|
+
</div>
|
|
133
|
+
<% end %>
|
|
134
|
+
|
|
135
|
+
<div class="mt-4 py-4 px-2 pb-8 border-t border-gray-100 flex">
|
|
136
|
+
<div class="flex gap-2 grow">
|
|
137
|
+
<%= form_for @product, url: solidus_admin.product_path(@product), method: :delete do %>
|
|
138
|
+
<%= render component("ui/button").new(
|
|
139
|
+
tag: :button,
|
|
140
|
+
text: t(".delete"),
|
|
141
|
+
scheme: :danger,
|
|
142
|
+
"data-action": "click->#{stimulus_id}#confirmDelete",
|
|
143
|
+
"data-#{stimulus_id}-message-param": t(".delete_confirmation"),
|
|
144
|
+
) %>
|
|
145
|
+
<% end %>
|
|
146
|
+
</div>
|
|
147
|
+
<%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::Products::Show::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
def initialize(product:)
|
|
5
|
+
@product = product
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def form_id
|
|
9
|
+
@form_id ||= "#{stimulus_id}--form-#{@product.id}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def taxon_options
|
|
15
|
+
@taxon_options ||= Spree::Taxon.order(:lft).pluck(:name, :id, :lft, :depth).map do
|
|
16
|
+
name, id, _lft, depth = _1
|
|
17
|
+
["#{' ' * depth} → #{name}", id]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def option_type_options
|
|
22
|
+
@option_type_options ||= Spree::OptionType.order(:presentation).pluck(:presentation, :name, :id).map do
|
|
23
|
+
["#{_1} (#{_2})", _3]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
en:
|
|
2
|
+
save: "Save"
|
|
3
|
+
back: "Back"
|
|
4
|
+
duplicate: "Duplicate"
|
|
5
|
+
view: "View online"
|
|
6
|
+
delete: "Delete"
|
|
7
|
+
none: "None"
|
|
8
|
+
manage_images: "Manage images"
|
|
9
|
+
manage_properties: "Manage product specifications"
|
|
10
|
+
manage_stock: "Manage stock"
|
|
11
|
+
delete_confirmation: "Are you sure you want to delete this product?"
|
|
12
|
+
hints:
|
|
13
|
+
available_on_html: This sets the availability date for the product. If this value is not set, or it is set to a date in the future, then the product is not available on the storefront.
|
|
14
|
+
discontinue_on_html: This sets the discontinue date for the product. If this value is set to a date, then the product is not available on the storefront from that day on anymore.
|
|
15
|
+
promotionable_html: 'This determines whether or not promotions can apply to this product.<br>Default: Checked'
|
|
16
|
+
shipping_category_html: 'This determines what kind of shipping this product requires.<br> Default: Default'
|
|
17
|
+
tax_category_html: 'This determines what kind of taxation is applied to this product.<br> Default: %{default_tax_category}'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SolidusAdmin::Products::Status::Component < SolidusAdmin::BaseComponent
|
|
4
|
+
COLORS = {
|
|
5
|
+
available: :green,
|
|
6
|
+
discontinued: :red
|
|
7
|
+
}.freeze
|
|
8
|
+
|
|
9
|
+
# @param product [Spree::Product]
|
|
10
|
+
def initialize(product:)
|
|
11
|
+
@product = product
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
render component('ui/badge').new(
|
|
16
|
+
name: t(".#{status}"),
|
|
17
|
+
color: COLORS.fetch(status)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Symbol]
|
|
22
|
+
# :available when the product is available
|
|
23
|
+
# :discontinued when the product is not available
|
|
24
|
+
def status
|
|
25
|
+
if @product.available?
|
|
26
|
+
:available
|
|
27
|
+
else
|
|
28
|
+
:discontinued
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|