solidus_admin 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|