solidus_admin 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -6
  3. data/Rakefile +1 -0
  4. data/app/assets/builds/solidus_admin/tailwind.css +198 -163
  5. data/app/assets/stylesheets/solidus_admin/dark.css +1 -0
  6. data/app/assets/stylesheets/solidus_admin/dimmed.css +1 -0
  7. data/app/components/solidus_admin/adjustment_reasons/edit/component.html.erb +27 -0
  8. data/app/components/solidus_admin/adjustment_reasons/edit/component.rb +12 -0
  9. data/app/components/solidus_admin/adjustment_reasons/edit/component.yml +8 -0
  10. data/app/components/solidus_admin/adjustment_reasons/index/component.rb +19 -2
  11. data/app/components/solidus_admin/adjustment_reasons/new/component.html.erb +28 -0
  12. data/app/components/solidus_admin/adjustment_reasons/new/component.rb +12 -0
  13. data/app/components/solidus_admin/adjustment_reasons/new/component.yml +8 -0
  14. data/app/components/solidus_admin/base_component.rb +0 -25
  15. data/app/components/solidus_admin/option_types/index/component.rb +1 -1
  16. data/app/components/solidus_admin/orders/index/component.rb +8 -8
  17. data/app/components/solidus_admin/orders/index/component.yml +1 -0
  18. data/app/components/solidus_admin/orders/show/address/component.html.erb +56 -54
  19. data/app/components/solidus_admin/orders/show/adjustments/index/adjustable/component.rb +27 -0
  20. data/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_line_item/component.rb +14 -0
  21. data/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_order/component.rb +11 -0
  22. data/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_shipment/component.rb +15 -0
  23. data/app/components/solidus_admin/orders/show/adjustments/index/component.rb +147 -0
  24. data/app/components/solidus_admin/orders/show/adjustments/index/component.yml +21 -0
  25. data/app/components/solidus_admin/orders/show/adjustments/index/source/component.rb +26 -0
  26. data/app/components/solidus_admin/orders/show/adjustments/index/source/spree_tax_rate/component.rb +17 -0
  27. data/app/components/solidus_admin/orders/show/adjustments/index/source/spree_unit_cancel/component.rb +7 -0
  28. data/app/components/solidus_admin/orders/show/component.html.erb +7 -3
  29. data/app/components/solidus_admin/orders/show/component.rb +8 -0
  30. data/app/components/solidus_admin/orders/show/email/component.html.erb +18 -13
  31. data/app/components/solidus_admin/orders/show/email/component.rb +0 -4
  32. data/app/components/solidus_admin/orders/show/summary/component.html.erb +1 -1
  33. data/app/components/solidus_admin/payment_methods/index/component.rb +1 -1
  34. data/app/components/solidus_admin/products/index/component.rb +8 -5
  35. data/app/components/solidus_admin/products/show/component.html.erb +14 -3
  36. data/app/components/solidus_admin/products/status/component.rb +4 -1
  37. data/app/components/solidus_admin/products/status/component.yml +1 -0
  38. data/app/components/solidus_admin/properties/index/component.rb +1 -1
  39. data/app/components/solidus_admin/refund_reasons/edit/component.html.erb +27 -0
  40. data/app/components/solidus_admin/refund_reasons/edit/component.rb +12 -0
  41. data/app/components/solidus_admin/refund_reasons/edit/component.yml +8 -0
  42. data/app/components/solidus_admin/refund_reasons/index/component.rb +11 -4
  43. data/app/components/solidus_admin/refund_reasons/new/component.html.erb +27 -0
  44. data/app/components/solidus_admin/refund_reasons/new/component.rb +12 -0
  45. data/app/components/solidus_admin/refund_reasons/new/component.yml +6 -0
  46. data/app/components/solidus_admin/return_reasons/edit/component.html.erb +26 -0
  47. data/app/components/solidus_admin/return_reasons/edit/component.rb +12 -0
  48. data/app/components/solidus_admin/return_reasons/edit/component.yml +8 -0
  49. data/app/components/solidus_admin/return_reasons/index/component.rb +20 -2
  50. data/app/components/solidus_admin/return_reasons/new/component.html.erb +27 -0
  51. data/app/components/solidus_admin/return_reasons/new/component.rb +12 -0
  52. data/app/components/solidus_admin/return_reasons/new/component.yml +8 -0
  53. data/app/components/solidus_admin/roles/edit/component.html.erb +33 -0
  54. data/app/components/solidus_admin/roles/edit/component.rb +20 -0
  55. data/app/components/solidus_admin/roles/edit/component.yml +19 -0
  56. data/app/components/solidus_admin/roles/index/component.rb +70 -0
  57. data/app/components/solidus_admin/roles/index/component.yml +6 -0
  58. data/app/components/solidus_admin/roles/new/component.html.erb +33 -0
  59. data/app/components/solidus_admin/roles/new/component.rb +20 -0
  60. data/app/components/solidus_admin/roles/new/component.yml +19 -0
  61. data/app/components/solidus_admin/shipping_categories/edit/component.html.erb +16 -0
  62. data/app/components/solidus_admin/shipping_categories/edit/component.rb +12 -0
  63. data/app/components/solidus_admin/shipping_categories/edit/component.yml +6 -0
  64. data/app/components/solidus_admin/shipping_categories/index/component.rb +20 -3
  65. data/app/components/solidus_admin/shipping_categories/new/component.html.erb +17 -0
  66. data/app/components/solidus_admin/shipping_categories/new/component.rb +12 -0
  67. data/app/components/solidus_admin/shipping_categories/new/component.yml +6 -0
  68. data/app/components/solidus_admin/shipping_methods/index/component.rb +2 -2
  69. data/app/components/solidus_admin/stock_items/edit/component.html.erb +66 -72
  70. data/app/components/solidus_admin/stock_items/edit/component.rb +0 -5
  71. data/app/components/solidus_admin/stock_items/index/component.rb +5 -6
  72. data/app/components/solidus_admin/stock_locations/index/component.rb +1 -1
  73. data/app/components/solidus_admin/store_credit_reasons/edit/component.html.erb +26 -0
  74. data/app/components/solidus_admin/store_credit_reasons/edit/component.rb +12 -0
  75. data/app/components/solidus_admin/store_credit_reasons/edit/component.yml +8 -0
  76. data/app/components/solidus_admin/store_credit_reasons/index/component.rb +11 -4
  77. data/app/components/solidus_admin/store_credit_reasons/new/component.html.erb +27 -0
  78. data/app/components/solidus_admin/store_credit_reasons/new/component.rb +12 -0
  79. data/app/components/solidus_admin/store_credit_reasons/new/component.yml +8 -0
  80. data/app/components/solidus_admin/stores/index/component.rb +1 -1
  81. data/app/components/solidus_admin/tax_categories/edit/component.html.erb +28 -0
  82. data/app/components/solidus_admin/tax_categories/edit/component.rb +12 -0
  83. data/app/components/solidus_admin/tax_categories/edit/component.yml +8 -0
  84. data/app/components/solidus_admin/tax_categories/index/component.rb +11 -4
  85. data/app/components/solidus_admin/tax_categories/new/component.html.erb +28 -0
  86. data/app/components/solidus_admin/tax_categories/new/component.rb +12 -0
  87. data/app/components/solidus_admin/tax_categories/new/component.yml +8 -0
  88. data/app/components/solidus_admin/tax_rates/index/component.rb +3 -3
  89. data/app/components/solidus_admin/taxonomies/index/component.rb +1 -1
  90. data/app/components/solidus_admin/ui/button/component.rb +1 -1
  91. data/app/components/solidus_admin/ui/checkbox_row/component.html.erb +29 -0
  92. data/app/components/solidus_admin/ui/checkbox_row/component.rb +11 -0
  93. data/app/components/solidus_admin/ui/forms/address/component.html.erb +27 -9
  94. data/app/components/solidus_admin/ui/forms/address/component.js +38 -13
  95. data/app/components/solidus_admin/ui/forms/search/component.html.erb +2 -3
  96. data/app/components/solidus_admin/ui/forms/search/component.js +3 -3
  97. data/app/components/solidus_admin/ui/modal/component.html.erb +7 -7
  98. data/app/components/solidus_admin/ui/modal/component.js +7 -0
  99. data/app/components/solidus_admin/ui/modal/component.rb +1 -1
  100. data/app/components/solidus_admin/ui/pages/index/component.html.erb +17 -3
  101. data/app/components/solidus_admin/ui/pages/index/component.rb +29 -43
  102. data/app/components/solidus_admin/ui/panel/component.html.erb +5 -16
  103. data/app/components/solidus_admin/ui/panel/component.rb +11 -7
  104. data/app/components/solidus_admin/ui/table/component.js +31 -2
  105. data/app/components/solidus_admin/ui/table/component.rb +23 -7
  106. data/app/components/solidus_admin/ui/table/component.yml +1 -0
  107. data/app/components/solidus_admin/ui/thumbnail/component.rb +1 -1
  108. data/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb +17 -0
  109. data/app/components/solidus_admin/ui/thumbnail_with_caption/component.rb +15 -0
  110. data/app/components/solidus_admin/users/addresses/component.html.erb +46 -0
  111. data/app/components/solidus_admin/users/addresses/component.rb +61 -0
  112. data/app/components/solidus_admin/users/addresses/component.yml +14 -0
  113. data/app/components/solidus_admin/users/edit/api_access/component.html.erb +49 -0
  114. data/app/components/solidus_admin/users/edit/api_access/component.js +9 -0
  115. data/app/components/solidus_admin/users/edit/api_access/component.rb +7 -0
  116. data/app/components/solidus_admin/users/edit/api_access/component.yml +10 -0
  117. data/app/components/solidus_admin/users/edit/component.html.erb +52 -0
  118. data/app/components/solidus_admin/users/edit/component.rb +51 -0
  119. data/app/components/solidus_admin/users/edit/component.yml +12 -0
  120. data/app/components/solidus_admin/users/index/component.rb +10 -8
  121. data/app/components/solidus_admin/users/items/component.html.erb +41 -0
  122. data/app/components/solidus_admin/users/items/component.rb +170 -0
  123. data/app/components/solidus_admin/users/items/component.yml +16 -0
  124. data/app/components/solidus_admin/users/orders/component.html.erb +42 -0
  125. data/app/components/solidus_admin/users/orders/component.rb +131 -0
  126. data/app/components/solidus_admin/users/orders/component.yml +12 -0
  127. data/app/components/solidus_admin/users/stats/component.html.erb +11 -0
  128. data/app/components/solidus_admin/users/stats/component.rb +9 -0
  129. data/app/components/solidus_admin/users/stats/component.yml +2 -0
  130. data/app/components/solidus_admin/users_and_roles/component.rb +24 -0
  131. data/app/components/solidus_admin/users_and_roles/component.yml +2 -0
  132. data/app/components/solidus_admin/zones/index/component.rb +1 -1
  133. data/app/controllers/solidus_admin/addresses_controller.rb +6 -1
  134. data/app/controllers/solidus_admin/adjustment_reasons_controller.rb +85 -10
  135. data/app/controllers/solidus_admin/adjustments_controller.rb +57 -0
  136. data/app/controllers/solidus_admin/customers_controller.rb +5 -1
  137. data/app/controllers/solidus_admin/orders_controller.rb +5 -1
  138. data/app/controllers/solidus_admin/products_controller.rb +11 -0
  139. data/app/controllers/solidus_admin/refund_reasons_controller.rb +85 -10
  140. data/app/controllers/solidus_admin/reimbursement_types_controller.rb +0 -5
  141. data/app/controllers/solidus_admin/return_reasons_controller.rb +85 -10
  142. data/app/controllers/solidus_admin/roles_controller.rb +118 -0
  143. data/app/controllers/solidus_admin/shipping_categories_controller.rb +87 -10
  144. data/app/controllers/solidus_admin/shipping_methods_controller.rb +0 -5
  145. data/app/controllers/solidus_admin/stock_items_controller.rb +6 -6
  146. data/app/controllers/solidus_admin/stock_locations_controller.rb +0 -5
  147. data/app/controllers/solidus_admin/store_credit_reasons_controller.rb +85 -10
  148. data/app/controllers/solidus_admin/stores_controller.rb +0 -5
  149. data/app/controllers/solidus_admin/tax_categories_controller.rb +89 -10
  150. data/app/controllers/solidus_admin/tax_rates_controller.rb +0 -5
  151. data/app/controllers/solidus_admin/users_controller.rb +85 -6
  152. data/app/controllers/solidus_admin/zones_controller.rb +0 -5
  153. data/app/helpers/solidus_admin/last_login_helper.rb +16 -0
  154. data/app/helpers/solidus_admin/permission_sets_helper.rb +32 -0
  155. data/app/views/layouts/solidus_admin/application.html.erb +2 -1
  156. data/config/locales/adjustment_reasons.en.yml +5 -1
  157. data/config/locales/adjustments.en.yml +10 -0
  158. data/config/locales/refund_reasons.en.yml +10 -0
  159. data/config/locales/return_reasons.en.yml +5 -1
  160. data/config/locales/roles.en.yml +10 -0
  161. data/config/locales/shipping_categories.en.yml +4 -0
  162. data/config/locales/store_credit_reasons.en.yml +5 -1
  163. data/config/locales/tax_categories.en.yml +4 -0
  164. data/config/locales/users.en.yml +10 -0
  165. data/config/routes.rb +24 -7
  166. data/docs/components.md +109 -0
  167. data/docs/{customizing_view_components.md → customizing_components.md} +2 -7
  168. data/docs/index_pages.md +146 -0
  169. data/docs/{customizing_menu_items.md → menu_items.md} +1 -1
  170. data/docs/stimulusjs.md +85 -0
  171. data/docs/{customizing_tailwindcss.md → tailwindcss.md} +27 -8
  172. data/lib/solidus_admin/component_registry.rb +40 -0
  173. data/lib/solidus_admin/configuration.rb +2 -26
  174. data/lib/solidus_admin/install_tailwindcss.rb +4 -2
  175. data/lib/solidus_admin/testing_support/admin_assets.rb +10 -0
  176. data/lib/solidus_admin/testing_support/component_helpers.rb +27 -0
  177. data/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb +60 -0
  178. data/lib/solidus_admin/testing_support/feature_helpers.rb +34 -0
  179. data/lib/solidus_admin/version.rb +1 -1
  180. data/solidus_admin.gemspec +7 -3
  181. metadata +108 -19
  182. data/app/components/solidus_admin/promotion_categories/index/component.rb +0 -56
  183. data/app/components/solidus_admin/promotions/index/component.rb +0 -104
  184. data/app/components/solidus_admin/promotions/index/component.yml +0 -10
  185. data/app/controllers/solidus_admin/promotion_categories_controller.rb +0 -29
  186. data/app/controllers/solidus_admin/promotions_controller.rb +0 -46
  187. data/config/locales/promotion_categories.en.yml +0 -6
  188. data/config/locales/promotions.en.yml +0 -6
  189. data/config/locales/refund_reasons_.en.yml +0 -6
@@ -3,12 +3,15 @@
3
3
  module SolidusAdmin
4
4
  class UsersController < SolidusAdmin::BaseController
5
5
  include SolidusAdmin::ControllerHelpers::Search
6
+ include Spree::Core::ControllerHelpers::StrongParameters
6
7
 
7
- search_scope(:customers, default: true) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) }
8
+ before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders, :items]
9
+
10
+ search_scope(:all, default: true)
11
+ search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) }
8
12
  search_scope(:admin) { _1.joins(:role_users).distinct }
9
13
  search_scope(:with_orders) { _1.joins(:orders).distinct }
10
14
  search_scope(:without_orders) { _1.left_outer_joins(:orders).where(orders: { id: nil }) }
11
- search_scope(:all)
12
15
 
13
16
  def index
14
17
  users = apply_search_to(
@@ -23,6 +26,52 @@ module SolidusAdmin
23
26
  end
24
27
  end
25
28
 
29
+ def addresses
30
+ respond_to do |format|
31
+ format.turbo_stream { render turbo_stream: '<turbo-stream action="refresh" />' }
32
+ format.html { render component('users/addresses').new(user: @user) }
33
+ end
34
+ end
35
+
36
+ def update_addresses
37
+ set_address_from_params
38
+
39
+ if @address.valid? && @user.update(user_params)
40
+ flash[:success] = t(".#{@type}.success")
41
+
42
+ respond_to do |format|
43
+ format.turbo_stream { render turbo_stream: '<turbo-stream action="refresh" />' }
44
+ format.html { render component('users/addresses').new(user: @user) }
45
+ end
46
+ else
47
+ respond_to do |format|
48
+ format.html { render component('users/addresses').new(user: @user, address: @address, type: @type), status: :unprocessable_entity }
49
+ end
50
+ end
51
+ end
52
+
53
+ def orders
54
+ set_orders
55
+
56
+ respond_to do |format|
57
+ format.html { render component('users/orders').new(user: @user, orders: @orders) }
58
+ end
59
+ end
60
+
61
+ def items
62
+ set_items
63
+
64
+ respond_to do |format|
65
+ format.html { render component('users/items').new(user: @user, items: @items) }
66
+ end
67
+ end
68
+
69
+ def edit
70
+ respond_to do |format|
71
+ format.html { render component('users/edit').new(user: @user) }
72
+ end
73
+ end
74
+
26
75
  def destroy
27
76
  @users = Spree.user_class.where(id: params[:id])
28
77
 
@@ -34,13 +83,43 @@ module SolidusAdmin
34
83
 
35
84
  private
36
85
 
37
- def load_user
38
- @user = Spree.user_class.find_by!(number: params[:id])
39
- authorize! action_name, @user
86
+ def set_user
87
+ @user = Spree.user_class.find(params[:id])
40
88
  end
41
89
 
42
90
  def user_params
43
- params.require(:user).permit(:user_id, permitted_user_attributes)
91
+ params.require(:user).permit(
92
+ :user_id,
93
+ permitted_user_attributes,
94
+ bill_address_attributes: permitted_address_attributes,
95
+ ship_address_attributes: permitted_address_attributes
96
+ )
97
+ end
98
+
99
+ # @note This method is used to generate validation errors on the address.
100
+ # Since the update is being performed via the @user, and not directly on
101
+ # the @address, we sadly don't seem to get these errors automatically.
102
+ def set_address_from_params
103
+ if user_params.key?(:bill_address_attributes)
104
+ @address = Spree::Address.new(user_params[:bill_address_attributes])
105
+ @type = "bill"
106
+ elsif user_params.key?(:ship_address_attributes)
107
+ @address = Spree::Address.new(user_params[:ship_address_attributes])
108
+ @type = "ship"
109
+ end
110
+ end
111
+
112
+ def set_orders
113
+ params[:q] ||= {}
114
+ @search = Spree::Order.reverse_chronological.ransack(params[:q].merge(user_id_eq: @user.id))
115
+ @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
116
+ end
117
+
118
+ def set_items
119
+ params[:q] ||= {}
120
+ @search = Spree::Order.reverse_chronological.includes(line_items: { variant: [:product, { option_values: :option_type }] }).ransack(params[:q].merge(user_id_eq: @user.id))
121
+ @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
122
+ @items = @orders&.map(&:line_items)&.flatten
44
123
  end
45
124
 
46
125
  def authorization_subject
@@ -28,11 +28,6 @@ module SolidusAdmin
28
28
 
29
29
  private
30
30
 
31
- def load_zone
32
- @zone = Spree::Zone.find_by!(number: params[:id])
33
- authorize! action_name, @zone
34
- end
35
-
36
31
  def zone_params
37
32
  params.require(:zone).permit(:zone_id, permitted_zone_attributes)
38
33
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAdmin
4
+ module LastLoginHelper
5
+ def last_login(user)
6
+ return t('solidus_admin.users.last_login.never') if user.try(:last_sign_in_at).blank?
7
+
8
+ t(
9
+ 'solidus_admin.users.last_login.login_time_ago',
10
+ # @note The second `.try` is here for the specs and for setups that use a
11
+ # custom User class which may not have this attribute.
12
+ last_login_time: time_ago_in_words(user.try(:last_sign_in_at))
13
+ ).capitalize
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusAdmin
4
+ module PermissionSetsHelper
5
+ # @param permission_sets [Array<Spree::PermissionSet>] an array of
6
+ # PermissionSet objects to be organized into categories based on their
7
+ # names.
8
+ # @param view_label [String] A string of your choice associated with "View"
9
+ # or "Display" level permissions. Used when rendering the checkbox.
10
+ # @param edit_label [String] A string of your choice associated with "Edit"
11
+ # or "Management" level permissions. Used when rendering the checkbox.
12
+ def organize_permissions(permission_sets:, view_label:, edit_label:)
13
+ return {} if permission_sets.blank?
14
+
15
+ permission_sets.each_with_object({}) do |permission, grouped_permissions|
16
+ group_key = permission.category.to_sym
17
+
18
+ case permission.privilege
19
+ when "display"
20
+ grouped_permissions[group_key] ||= []
21
+ grouped_permissions[group_key] << { label: view_label, id: permission.id }
22
+ when "management"
23
+ grouped_permissions[group_key] ||= []
24
+ grouped_permissions[group_key] << { label: edit_label, id: permission.id }
25
+ else
26
+ grouped_permissions[:other] ||= []
27
+ grouped_permissions[:other] << { label: permission.name, id: permission.id }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -11,6 +11,7 @@
11
11
  <%= stylesheet_link_tag SolidusAdmin::Config.theme_path(session[:admin_light_theme]), media: '(prefers-color-scheme: light)', "data-turbo-track": "reload" %>
12
12
  <%= stylesheet_link_tag SolidusAdmin::Config.theme_path(session[:admin_dark_theme]), media: '(prefers-color-scheme: dark)', "data-turbo-track": "reload" %>
13
13
  <%= javascript_importmap_tags "solidus_admin/application", shim: false, importmap: SolidusAdmin.importmap %>
14
+ <meta name="turbo-refresh-scroll", content="preserve">
14
15
  </head>
15
16
 
16
17
  <body class="bg-gray-15 font-sans">
@@ -29,7 +30,7 @@
29
30
 
30
31
  <div class="fixed inset-x-0 bottom-3 flex items-center justify-center flex-col gap-3 pointer-events-none" role="alert">
31
32
  <% flash.each do |key, message| %>
32
- <%= render component("ui/toast").new(text: message, scheme: key == :error ? :error : :default) %>
33
+ <%= render component("ui/toast").new(text: message, scheme: key.to_sym == :error ? :error : :default) %>
33
34
  <% end %>
34
35
  </div>
35
36
  </body>
@@ -3,4 +3,8 @@ en:
3
3
  adjustment_reasons:
4
4
  title: "Adjustment Reasons"
5
5
  destroy:
6
- success: "Adjustment Reasons were successfully removed."
6
+ success: "Adjustment reasons were successfully removed."
7
+ create:
8
+ success: "Adjustment reason was successfully created."
9
+ update:
10
+ success: "Adjustment reason was successfully updated."
@@ -0,0 +1,10 @@
1
+ en:
2
+ solidus_admin:
3
+ adjustments:
4
+ title: "Adjustments"
5
+ lock:
6
+ success: "Locked successfully"
7
+ unlock:
8
+ success: "Unlocked successfully"
9
+ destroy:
10
+ success: "Deleted successfully"
@@ -0,0 +1,10 @@
1
+ en:
2
+ solidus_admin:
3
+ refund_reasons:
4
+ title: "Refund Reasons"
5
+ destroy:
6
+ success: "Refund reasons were successfully removed."
7
+ create:
8
+ success: "Refund reason was successfully created."
9
+ update:
10
+ success: "Refund reason was successfully updated."
@@ -3,4 +3,8 @@ en:
3
3
  return_reasons:
4
4
  title: "Return Reasons"
5
5
  destroy:
6
- success: "Return Reasons were successfully removed."
6
+ success: "Return reasons were successfully removed."
7
+ create:
8
+ success: "Return reason was successfully created."
9
+ update:
10
+ success: "Return reason was successfully updated."
@@ -0,0 +1,10 @@
1
+ en:
2
+ solidus_admin:
3
+ roles:
4
+ title: "Roles"
5
+ destroy:
6
+ success: "Roles were successfully removed."
7
+ create:
8
+ success: "Role was successfully created."
9
+ update:
10
+ success: "Role was successfully updated."
@@ -4,3 +4,7 @@ en:
4
4
  title: "Shipping Categories"
5
5
  destroy:
6
6
  success: "Shipping categories were successfully removed."
7
+ create:
8
+ success: "Shipping category was successfully created."
9
+ update:
10
+ success: "Shipping category was successfully updated."
@@ -3,4 +3,8 @@ en:
3
3
  store_credit_reasons:
4
4
  title: "Store Credit Reasons"
5
5
  destroy:
6
- success: "Store Credit Reasons were successfully removed."
6
+ success: "Store credit reasons were successfully removed."
7
+ create:
8
+ success: "Store credit reason was successfully created."
9
+ update:
10
+ success: "Store credit reason was successfully updated."
@@ -4,3 +4,7 @@ en:
4
4
  title: "Tax Categories"
5
5
  destroy:
6
6
  success: "Tax categories were successfully removed."
7
+ create:
8
+ success: "Tax category was successfully created."
9
+ update:
10
+ success: "Tax category was successfully updated."
@@ -4,3 +4,13 @@ en:
4
4
  title: "Users"
5
5
  destroy:
6
6
  success: "Users were successfully removed."
7
+ update_addresses:
8
+ bill:
9
+ success: "Billing Address has been successfully updated."
10
+ ship:
11
+ success: "Shipping Address has been successfully updated."
12
+ error: "Address could not be updated."
13
+ last_login:
14
+ never: "Never"
15
+ login_time_ago: "%{last_login_time} ago"
16
+ invitation_sent: "Invitation sent"
data/config/routes.rb CHANGED
@@ -26,6 +26,14 @@ SolidusAdmin::Engine.routes.draw do
26
26
  admin_resources :orders, except: [
27
27
  :destroy, :index
28
28
  ], constraints: ->{ SolidusAdmin::Config.enable_alpha_features? } do
29
+ resources :adjustments, only: [:index] do
30
+ collection do
31
+ delete :destroy
32
+ put :lock
33
+ put :unlock
34
+ end
35
+ end
36
+
29
37
  resources :line_items, only: [:destroy, :create, :update]
30
38
  resource :customer
31
39
  resource :ship_address, only: [:show, :edit, :update], controller: "addresses", type: "ship"
@@ -37,24 +45,33 @@ SolidusAdmin::Engine.routes.draw do
37
45
  end
38
46
  end
39
47
 
40
- admin_resources :users, only: [:index, :destroy]
48
+ admin_resources :users, only: [:index, :edit, :destroy] do
49
+ member do
50
+ get :addresses
51
+ put :update_addresses
52
+ get :orders
53
+ get :items
54
+ end
55
+ end
56
+
41
57
  admin_resources :promotions, only: [:index, :destroy]
42
58
  admin_resources :properties, only: [:index, :destroy]
43
59
  admin_resources :option_types, only: [:index, :destroy], sortable: true
44
60
  admin_resources :taxonomies, only: [:index, :destroy], sortable: true
45
61
  admin_resources :promotion_categories, only: [:index, :destroy]
46
- admin_resources :tax_categories, only: [:index, :destroy]
62
+ admin_resources :tax_categories, except: [:show]
47
63
  admin_resources :tax_rates, only: [:index, :destroy]
48
64
  admin_resources :payment_methods, only: [:index, :destroy], sortable: true
49
65
  admin_resources :stock_items, only: [:index, :edit, :update]
50
66
  admin_resources :shipping_methods, only: [:index, :destroy]
51
- admin_resources :shipping_categories, only: [:index, :destroy]
67
+ admin_resources :shipping_categories, except: [:show]
52
68
  admin_resources :stock_locations, only: [:index, :destroy]
53
69
  admin_resources :stores, only: [:index, :destroy]
54
70
  admin_resources :zones, only: [:index, :destroy]
55
- admin_resources :refund_reasons, only: [:index, :destroy]
71
+ admin_resources :refund_reasons, except: [:show]
56
72
  admin_resources :reimbursement_types, only: [:index]
57
- admin_resources :return_reasons, only: [:index, :destroy]
58
- admin_resources :adjustment_reasons, only: [:index, :destroy]
59
- admin_resources :store_credit_reasons, only: [:index, :destroy]
73
+ admin_resources :return_reasons, except: [:show]
74
+ admin_resources :roles, except: [:show]
75
+ admin_resources :adjustment_reasons, except: [:show]
76
+ admin_resources :store_credit_reasons, except: [:show]
60
77
  end
@@ -0,0 +1,109 @@
1
+ # Components
2
+
3
+ Components are the main building blocks of the admin interface. They are implemented as ViewComponents and are rendered directly by controllers.
4
+
5
+ The following documentation assumes familiariry with ViewComponents. If you are not please refer to the [ViewComponent documentation](https://viewcomponent.org/guide/).
6
+
7
+ There are two types of components:
8
+
9
+ - **UI components** are the building blocks of the interface. Tipically, they are small components that are used to build more complex components and are generic enough to be reused in various contexts. UI components are located under the `app/components/solidus_admin/ui` folder.
10
+ - **Page components** are the primary components rendered by the controllers. Generally, they are full-page components rendered directly by the controllers. They are located under the `app/components/solidus_admin` directory following the naming convention of the controller and action names they are used in. For example `app/components/solidus_admin/orders/index/component.rb` is the component that is rendered by the `SolidusAdmin::OrdersController#index` action.
11
+
12
+ ## Generating components
13
+
14
+ Components can be generated using the `solidus_admin:component` generator combined with the `bin/rails` command from the Solidus repository.
15
+
16
+ ```shell
17
+ $ bin/rails admin g solidus_admin:component foo
18
+ create app/components/solidus_admin/foo/component.rb
19
+ create app/components/solidus_admin/foo/component.html.erb
20
+ create app/components/solidus_admin/foo/component.yml
21
+ create app/components/solidus_admin/foo/component.js
22
+ ```
23
+
24
+ Using `bin/rails admin` will run the generator from the `solidus_admin` engine, instead of the sandbox application.
25
+
26
+ ## Coding style
27
+
28
+ For UI components in particular, it's preferable to accept only simple Ruby values in the initializer and use alternative constructors to accept more complex objects. This makes components easier to use and test. For example:
29
+
30
+ ```ruby
31
+ # bad
32
+
33
+ class SolidusAdmin::UI::OrderStatus::Component < ViewComponent::Base
34
+ def initialize(order:)
35
+ @order = order
36
+ end
37
+ end
38
+
39
+ # good
40
+
41
+ class SolidusAdmin::UI::OrderStatus::Component < ViewComponent::Base
42
+ def initialize(status:)
43
+ @status = status
44
+ end
45
+
46
+ def self.for_order(order)
47
+ new(status: order.status)
48
+ end
49
+ end
50
+ ```
51
+
52
+ For style variations within the component we use the term `scheme` rather than `variant` to avoid confusion with product variants.
53
+
54
+ For size variations we use the term `size` with single letter values, e.g. `s`, `m`, `l`, `xl`, `xxl`.
55
+
56
+ For text content we use the term `text` rather than `name` to avoid confusion with the `name` attribute of the HTML tag.
57
+
58
+ ## Component registry
59
+
60
+ Components are registered in the `SolidusAdmin::Config.components` registry. This allows replacing components for customization purposes and components deprecation between versions.
61
+
62
+ To retrieve component classes from the registry, use the `component` helper within controllers and components that inherit from `SolidusAdmin::BaseComponent` or include `SolidusAdmin::ComponentHelper`. For example, `component('ui/button')` will fetch `SolidusAdmin::UI::Button::Component`.
63
+
64
+ ## When to use UI vs. Page components
65
+
66
+ Generally new components are built for a specific controller action and are used only within that action. In such cases, it's better to use a Page component and define it under the namespace of the action, e.g. `app/components/solidus_admin/orders/index/payment_status/component.rb`.
67
+
68
+ If a component is used by multiple actions of the same controller it can be moved to the controller namespace, e.g. `app/components/solidus_admin/orders/payment_status/component.rb`.
69
+
70
+ When a component is used by multiple controllers, you can either duplicate it in multiple places or move it to the `ui` namespace.
71
+
72
+ Although it may seem counterintuitive, duplicating the component can often be beneficial. This allows you to modify the component in one place without affecting other components that might be using it. Over time, the two copies may share enough generic code that it can be extracted into a UI component.
73
+
74
+ UI components should be very generic and reusable. However, they should not try to anticipate all possible use cases. Instead, they should be extracted from existing components that are already used in multiple places. This has proven to be the most effective way to build UI components. We've found that trying to anticipate theoretical use cases often leads to over-engineered code that eventually needs to be adapted to the actual use cases or is never used at all.
75
+
76
+ ## Naming conventions
77
+
78
+ The project uses a naming convention for components that slightly deviates from ViewComponent defaults. This is done to simplify renaming components and moving them around.
79
+
80
+ All files related to a component have a base name of `component`, each with its own extension. These files are placed in a folder named after the component class they define.
81
+
82
+
83
+ E.g. `app/components/solidus_admin/orders/index/payment_status/component.rb` defines the `SolidusAdmin::Orders::Index::PaymentStatus::Component` class.
84
+
85
+ With this approach, renaming a component is as simple as renaming the folder and the class name, without the need to change the names of all the files.
86
+
87
+ ## Translations
88
+
89
+ Components can define their own translations in the `component.yml` file and they're expected to be self contained. This means that translations defined in `solidus_core` should not be used in components.
90
+
91
+ Please refer to the [ViewComponent documentation](https://viewcomponent.org/guide/translations.html) for more information.
92
+
93
+ ## Previews and Lookbook
94
+
95
+ For UI components we leverage [ViewComponent previews](https://viewcomponent.org/guide/previews.html) combined with [Lookbook](https://lookbook.build) to provide a live preview of the component. This approach is highly beneficial for understanding the component's appearance and how it can be modified using different arguments.
96
+
97
+ Creating previews for page components can be challenging and prone to errors, as they often require a more complex context for rendering. Therefore, we typically don't use previews for page components, except for the most basic ones. However, if a component has a wide range of arguments and we want to cover all combinations, we might create a preview for it.
98
+
99
+ In order to inspect previews it's enough to visit `/lookbook` in the browser while the server is running.
100
+
101
+ ## Testing
102
+
103
+ Testing methods for components vary depending on whether they are UI or Page components. UI components are tested in isolation, while Page components, which often require a more complex context, are tested through feature specs.
104
+
105
+ For UI components, we use previews to achieve maximum coverage. This approach is sufficient for most basic components, but more complex components may require additional specs. This method has proven to minimize maintenance and code churn in the spec code, and it avoids repeating the code needed to render the component with different arguments.
106
+
107
+ Page components are tested in the context of the controller action they are used in. For example, `admin/spec/features/orders_spec.rb` covers interactions with the order listing and indirectly tests the `SolidusAdmin::Orders::Index::Component` class, among others.
108
+ We've found this to be the most effective way to test page components, as recreating the context needed for them in isolation can be difficult and prone to errors.
109
+ However, this is not a hard rule. If a Page component needs to be tested in isolation, or if a UI component requires a more complex context, you can always write additional specs.
@@ -1,4 +1,4 @@
1
- # Customizing view components
1
+ # Customizing components
2
2
 
3
3
  Solidus Admin uses [view components](https://viewcomponent.org/) to render the views. Components are
4
4
  a pattern for breaking up the view layer into small, reusable pieces, easy to
@@ -100,14 +100,9 @@ end
100
100
  If you need more control, you can explicitly register your component in the
101
101
  Solidus Admin container instead of using an implicit path:
102
102
 
103
- > ⓘ Right now, that will raise an error when the application is reloaded. We
104
- > need to fix it.
105
-
106
103
  ```ruby
107
104
  # config/initalizers/solidus_admin.rb
108
- Rails.application.config.to_prepare do
109
- SolidusAdmin::Config.components['ui/button'] = MyApplication::Button::Component
110
- end
105
+ SolidusAdmin::Config.components['ui/button'] = "MyApplication::Button::Component"
111
106
  ```
112
107
 
113
108
  ### Tweaking a component
@@ -0,0 +1,146 @@
1
+ # Adding index pages
2
+
3
+ Index pages are common in the admin interface, and they are used to display a list of records for a specific model.
4
+
5
+ Since these pages often have a similar appearance, we have a dedicated component to build them. This component helps avoid writing repetitive boilerplate code.
6
+
7
+ ## The `index` action
8
+
9
+ The `index` action is the standard action used to display index pages. It's a standard `GET` action that renders the `index` component.
10
+
11
+ To support search scopes and filters the controller should include the `SolidusAdmin::ControllerHelpers::Search` module and call
12
+ `apply_search_to` as follows:
13
+
14
+ ```ruby
15
+ class SolidusAdmin::UsersController < SolidusAdmin::BaseController
16
+ include SolidusAdmin::ControllerHelpers::Search
17
+
18
+ def index
19
+ users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
20
+ # ...
21
+ ```
22
+
23
+ For pagination support, the index action should also call the `set_page_and_extract_portion_from` method provided by the `geared_pagination` gem. This method sets the `@page` instance variable to the paginated collection and returns the portion of the collection to be displayed on the current page.
24
+
25
+ ```ruby
26
+ def index
27
+ users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
28
+ set_page_and_extract_portion_from(users)
29
+ # ...
30
+ ```
31
+
32
+ Finally, the index action should render the `index` component passing the `@page` instance variable as the `collection` prop.
33
+
34
+ ```ruby
35
+ def index
36
+ users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
37
+ set_page_and_extract_portion_from(users)
38
+ render component('users/index').new(page: @page)
39
+ end
40
+ ```
41
+
42
+ ## The `ui/pages/index` component
43
+
44
+ The `ui/pages/index` component is an abstract component that provides sensible defaults for index pages. It also offers template methods that can be used to customize the behavior of these pages.
45
+
46
+ We recommend examining existing index pages and the UI component itself to understand how they work. In this section, we'll cover the most important aspects.
47
+
48
+ The index component requires only the `page` argument during initialization, all other parameters are provided through template methods.
49
+
50
+ ```ruby
51
+ class SolidusAdmin::Users::Index < Solidus::Admin::UI::Pages::Index
52
+ def model_class
53
+ Spree.user_class
54
+ end
55
+ end
56
+
57
+ render component('users/index').new(page: @page)
58
+ ```
59
+
60
+ ## Batch Actions
61
+
62
+ Batch actions are operations that can be performed on multiple records simultaneously. The index page internally uses the `ui/table` component and depends on the `batch_actions` method to render the batch actions dropdown.
63
+
64
+ In the component, batch actions are provided as an array of hashes, with each hash representing a single batch action. Each hash must contain the following keys:
65
+
66
+ - `label`: the name of the batch action, this will be used as the label of the dropdown item
67
+ - `icon`: the remix icon-name to be used as the icon of the dropdown item (see the `ui/icon` component for more information)
68
+ - `action`: the name of the action to be performed when the batch action is selected. It can be a URL or a path
69
+ - `method`: the HTTP method to be used when performing the action, such as `:delete`
70
+
71
+ The `batch_actions` method is called in the context of the controller, so you can use any controller method or helper to build the batch actions.
72
+
73
+ Batch actions will be submitted to the specified action with an `id` parameter containing the IDs of the selected records. Using `id` as the
74
+ parameter name allows the same action to support both batch and single-record actions for standard routes.
75
+
76
+ E.g.
77
+
78
+ ```ruby
79
+ # in the component
80
+ def batch_actions
81
+ [
82
+ {
83
+ label: "Delete",
84
+ icon: "trash",
85
+ action: solidus_admin.delete_admin_users_path,
86
+ method: :delete
87
+ }
88
+ ]
89
+ end
90
+ ```
91
+
92
+ ```ruby
93
+ # in the controller
94
+ def delete
95
+ @users = Spree.user_class.where(id: params[:id])
96
+ @users.destroy_all
97
+ flash[:success] = "Admin users deleted"
98
+ redirect_to solidus_admin.users_path, status: :see_other
99
+ end
100
+ ```
101
+
102
+ ## Search Scopes
103
+
104
+ Search scopes are used to filter the records displayed on the index page. The index page internally uses the `ui/table` component and relies on the `scopes` method to render the search scope buttons.
105
+
106
+ In the component, search scopes are provided as an array of hashes, with each hash representing a single search scope. Each hash must contain the following keys:
107
+
108
+ - `label`: the name of the search scope, used as the label of the button
109
+ - `name`: the name of the search scope, sent via `q[scope]` parameter when the button is clicked
110
+ - `default`: whether this is the default search scope, used to highlight the button when when the page is loaded
111
+
112
+ On the controller side, search scopes can be defined with the `search_scope` helper, provided by `SolidusAdmin::ControllerHelpers::Search`. This helper takes a name, an optional `default` keyword argument, and a block. The block will be called with the current scope and should return a new ActiveRecord scope.
113
+ E.g.
114
+
115
+ ```ruby
116
+ module SolidusAdmin
117
+ class UsersController < SolidusAdmin::BaseController
118
+ include SolidusAdmin::ControllerHelpers::Search
119
+
120
+ search_scope(:customers, default: true) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) }
121
+ search_scope(:admin) { _1.joins(:role_users).distinct }
122
+ search_scope(:with_orders) { _1.joins(:orders).distinct }
123
+ search_scope(:without_orders) { _1.left_outer_joins(:orders).where(orders: { id: nil }) }
124
+ search_scope(:all)
125
+
126
+ def index
127
+ users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
128
+ # ...
129
+ ```
130
+
131
+ ## Filters
132
+
133
+ Filters are used to narrow down the records displayed on the index page. The index page internally uses the `ui/table/ransack_filter` component and depends on the `filters` method to render the filters dropdown.
134
+
135
+ In the component, filters are represented as an array of hashes, with each hash representing a single filter. Each hash must contain the following keys:
136
+
137
+
138
+ - `label`: the name of the filter, used as the label in the filter bar
139
+ - `attribute`: the name of the ransack-enabled attribute to be filtered
140
+ - `predicate`: the name of the ransack predicate to be used, e.g. `eq`, `in`, `cont`
141
+ - `options`: an array of options to be used for the filter, in the standard rails form of `[["label", "value"], ...]`
142
+
143
+ On the controller side it's enough to add ransack support to the index action by including `SolidusAdmin::ControllerHelpers::Search` and calling
144
+ `apply_search_to` as explained in the [Index action](#index-action) section.
145
+
146
+ On the controller side, you simply need to add Ransack support to the index action by including the `SolidusAdmin::ControllerHelpers::Search` module and calling the `apply_search_to` method, as explained in the [Index action](#index-action) section.
@@ -1,4 +1,4 @@
1
- # Customizing the main navigation
1
+ # Customizing the Menu
2
2
 
3
3
  You are allowed to add your custom links to the main navigation. To do so, you can access `SolidusAdmin::Config.menu_items` in an initializer:
4
4