katalyst-koi 4.18.1 → 5.0.0.alpha.2

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.
Files changed (222) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/koi/icons/add.svg +3 -0
  3. data/app/assets/images/koi/icons/close.svg +1 -0
  4. data/app/assets/images/koi/koi.png +0 -0
  5. data/app/assets/javascripts/koi/controllers/file_field_controller.js +2 -2
  6. data/app/assets/javascripts/koi/controllers/index.js +0 -3
  7. data/app/assets/javascripts/koi/controllers/koi/modal_controller.js +40 -0
  8. data/app/assets/javascripts/koi/controllers/navigation_controller.js +14 -21
  9. data/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +4 -1
  10. data/app/assets/stylesheets/koi/blocks/actions.css +8 -0
  11. data/app/assets/stylesheets/koi/blocks/application-header.css +15 -0
  12. data/app/assets/stylesheets/koi/blocks/application-navigation.css +54 -0
  13. data/app/assets/stylesheets/koi/blocks/button.css +90 -0
  14. data/app/assets/stylesheets/koi/blocks/flash.css +19 -0
  15. data/app/assets/stylesheets/koi/blocks/icon.css +15 -0
  16. data/app/assets/stylesheets/koi/blocks/index.css +13 -0
  17. data/app/assets/stylesheets/koi/blocks/modal.css +26 -0
  18. data/app/assets/stylesheets/koi/blocks/navigation.css +23 -0
  19. data/app/assets/stylesheets/koi/blocks/page-header.css +31 -0
  20. data/app/assets/stylesheets/koi/blocks/pagy.css +82 -0
  21. data/app/assets/stylesheets/koi/blocks/prose.css +37 -0
  22. data/app/assets/stylesheets/koi/blocks/tables/index.css +4 -0
  23. data/app/assets/stylesheets/koi/{components/_query.scss → blocks/tables/query.css} +13 -13
  24. data/app/assets/stylesheets/koi/{base/_tables.scss → blocks/tables/table.css} +12 -60
  25. data/app/assets/stylesheets/koi/compositions/cover.css +17 -0
  26. data/app/assets/stylesheets/koi/{base/_flow.scss → compositions/flow.css} +1 -1
  27. data/app/assets/stylesheets/koi/compositions/index.css +4 -0
  28. data/app/assets/stylesheets/koi/compositions/wrapper.css +11 -0
  29. data/app/assets/stylesheets/koi/forms/caption.css +22 -0
  30. data/app/assets/stylesheets/koi/forms/checkboxes.css +153 -0
  31. data/app/assets/stylesheets/koi/forms/date-input.css +12 -0
  32. data/app/assets/stylesheets/koi/{components/_document-field.scss → forms/document-field.css} +20 -15
  33. data/app/assets/stylesheets/koi/forms/errors.css +38 -0
  34. data/app/assets/stylesheets/koi/forms/fieldset.css +73 -0
  35. data/app/assets/stylesheets/koi/forms/file-upload.css +20 -0
  36. data/app/assets/stylesheets/koi/forms/form-group.css +19 -0
  37. data/app/assets/stylesheets/koi/forms/hint.css +11 -0
  38. data/app/assets/stylesheets/koi/forms/image-field.css +96 -0
  39. data/app/assets/stylesheets/koi/forms/index.css +44 -0
  40. data/app/assets/stylesheets/koi/forms/input.css +194 -0
  41. data/app/assets/stylesheets/koi/forms/label.css +43 -0
  42. data/app/assets/stylesheets/koi/forms/password.css +18 -0
  43. data/app/assets/stylesheets/koi/forms/radios.css +162 -0
  44. data/app/assets/stylesheets/koi/forms/select.css +18 -0
  45. data/app/assets/stylesheets/koi/forms/textarea.css +3 -0
  46. data/app/assets/stylesheets/koi/forms/trix.css +33 -0
  47. data/app/assets/stylesheets/koi/global/fonts.css +22 -0
  48. data/app/assets/stylesheets/koi/global/global-styles.css +297 -0
  49. data/app/assets/stylesheets/koi/global/reset.css +98 -0
  50. data/app/assets/stylesheets/koi/global/variables.css +97 -0
  51. data/app/assets/stylesheets/koi/icons.css +14 -0
  52. data/app/assets/stylesheets/koi/index.css +17 -0
  53. data/app/assets/stylesheets/koi/login.css +26 -0
  54. data/app/assets/stylesheets/koi/utilities/index.css +1 -0
  55. data/app/assets/stylesheets/koi/utilities/visually-hidden.css +18 -0
  56. data/app/components/concerns/koi/tables/cells.rb +3 -3
  57. data/app/components/koi/header_component.html.erb +12 -11
  58. data/app/components/koi/header_component.rb +2 -0
  59. data/app/components/koi/table_component.rb +8 -0
  60. data/app/controllers/admin/admin_users_controller.rb +24 -18
  61. data/app/controllers/admin/application_controller.rb +1 -3
  62. data/app/controllers/admin/credentials_controller.rb +18 -14
  63. data/app/controllers/admin/otps_controller.rb +15 -13
  64. data/app/controllers/admin/sessions_controller.rb +12 -1
  65. data/app/controllers/admin/url_rewrites_controller.rb +19 -17
  66. data/app/controllers/admin/well_knowns_controller.rb +20 -18
  67. data/app/controllers/concerns/koi/controller.rb +37 -0
  68. data/app/helpers/koi/form_helper.rb +18 -0
  69. data/app/helpers/koi/header_helper.rb +122 -0
  70. data/app/helpers/koi/index_actions_helper.rb +3 -2
  71. data/app/helpers/koi/modal_helper.rb +71 -0
  72. data/app/models/admin/user.rb +7 -1
  73. data/app/models/url_rewrite.rb +1 -9
  74. data/app/views/admin/admin_users/_form.html+self.erb +8 -0
  75. data/app/views/admin/admin_users/_form.html.erb +8 -0
  76. data/app/views/admin/admin_users/archived.html.erb +7 -4
  77. data/app/views/admin/admin_users/edit.html+self.erb +12 -0
  78. data/app/views/admin/admin_users/edit.html.erb +13 -8
  79. data/app/views/admin/admin_users/index.html.erb +10 -5
  80. data/app/views/admin/admin_users/new.html.erb +8 -8
  81. data/app/views/admin/admin_users/show.html+self.erb +26 -14
  82. data/app/views/admin/admin_users/show.html.erb +22 -20
  83. data/app/views/admin/credentials/_credentials.html+self.erb +8 -6
  84. data/app/views/admin/credentials/_credentials.html.erb +3 -1
  85. data/app/views/admin/credentials/create.turbo_stream.erb +4 -3
  86. data/app/views/admin/credentials/destroy.turbo_stream.erb +4 -2
  87. data/app/views/admin/credentials/new.html.erb +42 -36
  88. data/app/views/admin/dashboards/show.html.erb +13 -1
  89. data/app/views/admin/otps/_form.html.erb +7 -7
  90. data/app/views/admin/otps/create.turbo_stream.erb +3 -3
  91. data/app/views/admin/otps/new.html.erb +5 -3
  92. data/app/views/admin/sessions/new.html.erb +2 -3
  93. data/app/views/admin/sessions/otp.html.erb +1 -3
  94. data/app/views/admin/sessions/password.html.erb +1 -3
  95. data/app/views/admin/tokens/show.html.erb +4 -6
  96. data/app/views/admin/url_rewrites/_form.html.erb +9 -0
  97. data/app/views/admin/url_rewrites/edit.html.erb +13 -9
  98. data/app/views/admin/url_rewrites/index.html.erb +10 -7
  99. data/app/views/admin/url_rewrites/new.html.erb +8 -8
  100. data/app/views/admin/url_rewrites/show.html.erb +17 -12
  101. data/app/views/admin/well_knowns/_form.html.erb +9 -0
  102. data/app/views/admin/well_knowns/edit.html.erb +13 -9
  103. data/app/views/admin/well_knowns/index.html.erb +8 -5
  104. data/app/views/admin/well_knowns/new.html.erb +8 -8
  105. data/app/views/admin/well_knowns/show.html.erb +14 -13
  106. data/app/views/katalyst/content/asides/_aside.html+form.erb +6 -4
  107. data/app/views/katalyst/content/columns/_column.html+form.erb +5 -3
  108. data/app/views/katalyst/content/contents/_content.html+form.erb +8 -6
  109. data/app/views/katalyst/content/figures/_figure.html+form.erb +8 -5
  110. data/app/views/katalyst/content/groups/_group.html+form.erb +5 -3
  111. data/app/views/katalyst/content/items/_item.html+form.erb +5 -3
  112. data/app/views/katalyst/content/sections/_section.html+form.erb +5 -3
  113. data/app/views/katalyst/content/tables/_table.html+form.erb +16 -11
  114. data/app/views/katalyst/navigation/items/_button.html.erb +6 -12
  115. data/app/views/katalyst/navigation/items/_heading.html.erb +3 -10
  116. data/app/views/katalyst/navigation/items/_link.html.erb +6 -11
  117. data/app/views/katalyst/navigation/menus/edit.html.erb +10 -6
  118. data/app/views/katalyst/navigation/menus/index.html.erb +4 -2
  119. data/app/views/katalyst/navigation/menus/new.html.erb +5 -3
  120. data/app/views/katalyst/navigation/menus/show.html.erb +8 -7
  121. data/app/views/layouts/koi/_application_header.html.erb +20 -0
  122. data/app/views/layouts/koi/_application_navigation.html.erb +34 -0
  123. data/app/views/layouts/koi/_flash.html.erb +6 -3
  124. data/app/views/layouts/koi/_navigation_header.html.erb +0 -2
  125. data/app/views/layouts/koi/application.html.erb +22 -27
  126. data/app/views/layouts/koi/frame.html.erb +1 -3
  127. data/app/views/layouts/koi/login.html.erb +12 -5
  128. data/config/locales/koi.en.yml +9 -1
  129. data/config/routes.rb +1 -1
  130. data/lib/generators/koi/admin/admin_generator.rb +3 -13
  131. data/lib/generators/koi/admin_controller/admin_controller_generator.rb +6 -16
  132. data/lib/generators/koi/admin_controller/templates/controller.rb.tt +82 -18
  133. data/lib/generators/koi/admin_controller/templates/controller_spec.rb.tt +113 -47
  134. data/lib/generators/koi/admin_route/admin_route_generator.rb +60 -6
  135. data/lib/generators/koi/admin_views/USAGE +18 -7
  136. data/lib/generators/koi/admin_views/admin_views_generator.rb +19 -11
  137. data/lib/generators/koi/admin_views/templates/_form.html.erb.tt +8 -0
  138. data/lib/generators/koi/admin_views/templates/archived.html.erb.tt +33 -0
  139. data/lib/generators/koi/admin_views/templates/edit.html.erb.tt +17 -9
  140. data/lib/generators/koi/admin_views/templates/index.html.erb.tt +31 -3
  141. data/lib/generators/koi/admin_views/templates/new.html.erb.tt +8 -8
  142. data/lib/generators/koi/admin_views/templates/show.html.erb.tt +15 -18
  143. data/lib/generators/koi/helpers/attribute_helpers.rb +147 -0
  144. data/lib/generators/koi/helpers/attribute_types.rb +218 -0
  145. data/lib/generators/koi/helpers/resource_helpers.rb +121 -0
  146. data/lib/generators/koi/{active_record/active_record_generator.rb → model/model_generator.rb} +1 -1
  147. data/lib/koi/config.rb +3 -1
  148. data/lib/koi/engine.rb +0 -9
  149. data/lib/koi/form/builder.rb +4 -4
  150. data/lib/koi/form/content.rb +55 -0
  151. data/lib/koi/form/elements/document.rb +1 -1
  152. data/lib/koi/form/elements/image.rb +1 -1
  153. data/lib/koi/form_builder.rb +1 -0
  154. data/lib/koi/menu.rb +14 -1
  155. data/spec/factories/admins.rb +1 -1
  156. metadata +90 -103
  157. data/app/assets/builds/koi/admin.css +0 -1
  158. data/app/assets/stylesheets/koi/admin.scss +0 -27
  159. data/app/assets/stylesheets/koi/base/_button.scss +0 -122
  160. data/app/assets/stylesheets/koi/base/_icon.scss +0 -29
  161. data/app/assets/stylesheets/koi/base/_index.scss +0 -21
  162. data/app/assets/stylesheets/koi/base/_input.scss +0 -19
  163. data/app/assets/stylesheets/koi/base/_link.scss +0 -26
  164. data/app/assets/stylesheets/koi/base/_list.scss +0 -11
  165. data/app/assets/stylesheets/koi/base/_typography.scss +0 -160
  166. data/app/assets/stylesheets/koi/components/_actions-group.scss +0 -7
  167. data/app/assets/stylesheets/koi/components/_image-field.scss +0 -95
  168. data/app/assets/stylesheets/koi/components/_index.scss +0 -9
  169. data/app/assets/stylesheets/koi/components/_pagy.scss +0 -29
  170. data/app/assets/stylesheets/koi/components/_summary-list.scss +0 -40
  171. data/app/assets/stylesheets/koi/layouts/_banner.scss +0 -7
  172. data/app/assets/stylesheets/koi/layouts/_content.scss +0 -40
  173. data/app/assets/stylesheets/koi/layouts/_flash.scss +0 -41
  174. data/app/assets/stylesheets/koi/layouts/_header.scss +0 -61
  175. data/app/assets/stylesheets/koi/layouts/_index.scss +0 -48
  176. data/app/assets/stylesheets/koi/layouts/_main.scss +0 -23
  177. data/app/assets/stylesheets/koi/layouts/_navigation.scss +0 -180
  178. data/app/assets/stylesheets/koi/layouts/_stack.scss +0 -13
  179. data/app/assets/stylesheets/koi/pages/_index.scss +0 -1
  180. data/app/assets/stylesheets/koi/pages/_login.scss +0 -46
  181. data/app/assets/stylesheets/koi/themes/_content.scss +0 -62
  182. data/app/assets/stylesheets/koi/themes/_govuk.scss +0 -56
  183. data/app/assets/stylesheets/koi/themes/_index.scss +0 -5
  184. data/app/assets/stylesheets/koi/themes/_kpop.scss +0 -5
  185. data/app/assets/stylesheets/koi/themes/_navigation.scss +0 -5
  186. data/app/assets/stylesheets/koi/themes/_trix.scss +0 -32
  187. data/app/assets/stylesheets/koi/utils/_breakpoints.scss +0 -13
  188. data/app/assets/stylesheets/koi/utils/_hide.scss +0 -11
  189. data/app/assets/stylesheets/koi/utils/_index.scss +0 -2
  190. data/app/assets/stylesheets/koi/utils/_typography.scss +0 -42
  191. data/app/components/koi/content/editor/item_form_component.html.erb +0 -11
  192. data/app/components/koi/content/editor/item_form_component.rb +0 -94
  193. data/app/components/koi/summary_list/attachment_component.rb +0 -47
  194. data/app/components/koi/summary_list/base.rb +0 -59
  195. data/app/components/koi/summary_list/boolean_component.rb +0 -15
  196. data/app/components/koi/summary_list/date_component.rb +0 -17
  197. data/app/components/koi/summary_list/datetime_component.rb +0 -17
  198. data/app/components/koi/summary_list/item_component.rb +0 -26
  199. data/app/components/koi/summary_list/number_component.rb +0 -21
  200. data/app/components/koi/summary_list/rich_text_component.rb +0 -8
  201. data/app/components/koi/summary_list/text_component.rb +0 -8
  202. data/app/components/koi/summary_list_component.html.erb +0 -5
  203. data/app/components/koi/summary_list_component.rb +0 -75
  204. data/app/controllers/concerns/koi/controller/is_admin_controller.rb +0 -66
  205. data/app/helpers/koi/application_helper.rb +0 -7
  206. data/app/helpers/koi/date_helper.rb +0 -26
  207. data/app/helpers/koi/definition_list_helper.rb +0 -10
  208. data/app/views/admin/admin_users/_fields.html+self.erb +0 -3
  209. data/app/views/admin/admin_users/_fields.html.erb +0 -3
  210. data/app/views/admin/url_rewrites/_fields.html.erb +0 -4
  211. data/app/views/admin/well_knowns/_fields.html.erb +0 -6
  212. data/app/views/layouts/koi/_environment.html.erb +0 -4
  213. data/app/views/layouts/koi/_header.html.erb +0 -11
  214. data/app/views/layouts/koi/_navigation.html.erb +0 -23
  215. data/app/views/layouts/koi/_navigation_collapse.html.erb +0 -3
  216. data/lib/generators/koi/admin_views/templates/_fields.html.erb.tt +0 -3
  217. data/lib/generators/koi/helpers/admin_generator_attributes.rb +0 -66
  218. data/lib/koi/extensions/dartsass.rb +0 -23
  219. /data/app/assets/stylesheets/koi/{components/_clipboard.scss → blocks/clipboard.css} +0 -0
  220. /data/app/assets/stylesheets/koi/{components/_index-actions.scss → blocks/index-actions.css} +0 -0
  221. /data/app/assets/stylesheets/koi/{components/_toolbar.scss → blocks/toolbar.css} +0 -0
  222. /data/app/assets/stylesheets/koi/{base/_repel.scss → compositions/repel.css} +0 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Koi
4
+ module ModalHelper
5
+ def koi_modal_tag(frame_id = "modal", frame: {}, dialog: {}, **attributes, &block)
6
+ if block
7
+ turbo_frame_tag(frame_id, **_koi_modal_frame_attributes(frame)) do
8
+ tag.dialog(**_koi_modal_dialog_attributes(dialog)) do
9
+ tag.article(**_koi_modal_article_attributes(attributes), &block)
10
+ end
11
+ end
12
+ else
13
+ turbo_frame_tag(frame_id, **_koi_modal_frame_attributes(frame))
14
+ end
15
+ end
16
+
17
+ def koi_modal_header(title:, form_id:, **)
18
+ tag.header(class: "repel", data: "nowrap") do
19
+ concat(tag.h2(title))
20
+ concat(tag.button(
21
+ form: form_id,
22
+ formmethod: "dialog",
23
+ class: "button",
24
+ data: { button_padding: "tight",
25
+ "text-button": "" },
26
+ ) do
27
+ tag.icon(" ".html_safe, aria: { hidden: true }, class: "icon", data: { icon: "close" }) +
28
+ tag.span("Close", class: "visually-hidden")
29
+ end)
30
+ end
31
+ end
32
+
33
+ def koi_modal_footer(submit = "Save", discard = "Discard", form_id:, reverse: true)
34
+ tag.footer(class: "actions", data: { reverse: ("" if reverse) }) do
35
+ concat(tag.button(submit, form: form_id, class: "button", data: { close_dialog: "" }))
36
+ if discard.present?
37
+ concat(tag.button(discard, form: form_id, formmethod: "dialog", class: "button", data: { ghost_button: "" }))
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ using Katalyst::HtmlAttributes::HasHtmlAttributes
45
+
46
+ def _koi_modal_frame_attributes(attributes)
47
+ {
48
+ data: { controller: "koi--modal" },
49
+ }.merge_html(attributes)
50
+ end
51
+
52
+ def _koi_modal_dialog_attributes(attributes)
53
+ {
54
+ class: "modal",
55
+ data: {
56
+ koi__modal_target: "dialog",
57
+ action: %w[
58
+ click->koi--modal#click
59
+ close->koi--modal#dismiss
60
+ ],
61
+ },
62
+ }.merge_html(attributes)
63
+ end
64
+
65
+ def _koi_modal_article_attributes(attributes)
66
+ {
67
+ class: "flow",
68
+ }.merge_html(attributes)
69
+ end
70
+ end
71
+ end
@@ -6,9 +6,11 @@ module Admin
6
6
  include Koi::Model::OTP
7
7
 
8
8
  def self.model_name
9
- ActiveModel::Name.new(self, nil, "Admin")
9
+ ActiveModel::Name.new(self, nil, "AdminUser")
10
10
  end
11
11
 
12
+ self.table_name = :admins
13
+
12
14
  # disable validations for password_digest
13
15
  has_secure_password validations: false
14
16
 
@@ -51,5 +53,9 @@ module Admin
51
53
  credentials.any?
52
54
  end
53
55
  alias passkey? passkey
56
+
57
+ def to_s
58
+ name
59
+ end
54
60
  end
55
61
  end
@@ -1,15 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UrlRewrite < ApplicationRecord
4
- StatusCode = Struct.new(:code, :label)
5
-
6
- STATUS_CODES = [
7
- StatusCode.new(303, "See other"),
8
- StatusCode.new(307, "Temporary redirect"),
9
- StatusCode.new(308, "Permanent redirect"),
10
- ].freeze
11
-
12
- enum :status_code, STATUS_CODES.map(&:code).index_by(&:itself)
4
+ enum :status_code, [303, 307, 308].index_by(&:itself)
13
5
 
14
6
  attribute :status_code, :integer, default: 303
15
7
 
@@ -0,0 +1,8 @@
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <%= form_with(model: admin_user) do |form| %>
4
+ <%= form.govuk_text_field :email %>
5
+ <%= form.govuk_text_field :name %>
6
+ <%= form.govuk_password_field :password, label: { text: "Password (optional)" } %>
7
+ <%= form.button(type: :submit, class: "button") %>
8
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <%= form_with(model: admin_user) do |form| %>
4
+ <%= form.govuk_text_field :email %>
5
+ <%= form.govuk_text_field :name %>
6
+ <%= form.govuk_check_box_field :archived if form.object.persisted? %>
7
+ <%= form.button(type: :submit, class: "button") %>
8
+ <% end %>
@@ -1,13 +1,16 @@
1
- <% content_for :header do %>
2
- <%= render Koi::Header::IndexComponent.new(model: Admin::User, title: "Archived") do |component| %>
3
- <% component.with_breadcrumb "Admins", admin_admin_users_path %>
1
+ <%# locals: (collection:) %>
2
+
3
+ <% content_for(:header) do %>
4
+ <%= breadcrumb_list do %>
5
+ <li><%= link_to("Admin users", admin_admin_users_path) %></li>
4
6
  <% end %>
7
+ <h1>Archived admin users</h1>
5
8
  <% end %>
6
9
 
7
10
  <%= table_query_with(collection:) %>
8
11
 
9
12
  <%= table_selection_with(collection:) do %>
10
- <%= tag.button "Restore", formaction: restore_admin_admin_users_path, formmethod: :put %>
13
+ <%= tag.button("Restore", formaction: restore_admin_admin_users_path, formmethod: :put, class: "button") %>
11
14
  <% end %>
12
15
 
13
16
  <%= table_with(collection:) do |row| %>
@@ -0,0 +1,12 @@
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <% content_for(:header) do %>
4
+ <%= breadcrumb_list do %>
5
+ <li><%= link_to("Admin users", admin_admin_users_path) %></li>
6
+ <li><%= link_to(admin_user, admin_admin_user_path(admin_user)) %></li>
7
+ <% end %>
8
+
9
+ <h1>Edit profile</h1>
10
+ <% end %>
11
+
12
+ <%= render("form", admin_user:) %>
@@ -1,11 +1,16 @@
1
- <% content_for :header do %>
2
- <%= render Koi::Header::EditComponent.new(resource: admin) %>
3
- <% end %>
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <% content_for(:header) do %>
4
+ <%= breadcrumb_list do %>
5
+ <li><%= link_to("Admin users", admin_admin_users_path) %></li>
6
+ <li><%= link_to(admin_user, admin_admin_user_path(admin_user)) %></li>
7
+ <% end %>
4
8
 
5
- <%= form_with(model: admin, url: admin_admin_user_path(admin)) do |form| %>
6
- <%= render "fields", form: %>
9
+ <h1>Edit admin user</h1>
7
10
 
8
- <div class="actions">
9
- <%= form.admin_save %>
10
- </div>
11
+ <%= actions_list do %>
12
+ <li><%= link_to_archive_or_delete(admin_user) %></li>
13
+ <% end %>
11
14
  <% end %>
15
+
16
+ <%= render("form", admin_user:) %>
@@ -1,14 +1,18 @@
1
- <% content_for :header do %>
2
- <%= render Koi::Header::IndexComponent.new(model: Admin::User) do |component| %>
3
- <% component.with_action "New", new_admin_admin_user_path %>
4
- <% component.with_action "Archived", archived_admin_admin_users_path %>
1
+ <%# locals: (collection:) %>
2
+
3
+ <% content_for(:header) do %>
4
+ <h1>Admin users</h1>
5
+
6
+ <%= actions_list do %>
7
+ <li><%= link_to("New", new_admin_admin_user_path) %></li>
8
+ <li><%= link_to("Archived", archived_admin_admin_users_path) %></li>
5
9
  <% end %>
6
10
  <% end %>
7
11
 
8
12
  <%= table_query_with(collection:) %>
9
13
 
10
14
  <%= table_selection_with(collection:) do %>
11
- <%= tag.button "Archive", formaction: archive_admin_admin_users_path, formmethod: :put %>
15
+ <%= tag.button("Archive", formaction: archive_admin_admin_users_path, formmethod: :put, class: "button") %>
12
16
  <% end %>
13
17
 
14
18
  <%= table_with(collection:) do |row| %>
@@ -21,6 +25,7 @@
21
25
  <% row.boolean :otp, label: "MFA" do |cell| %>
22
26
  <%= cell.value.present? ? "Yes" : "No" %>
23
27
  <% end %>
28
+ <% row.date :last_sign_in_at, label: "Last active" %>
24
29
  <% end %>
25
30
 
26
31
  <%= table_pagination_with(collection:) %>
@@ -1,11 +1,11 @@
1
- <% content_for :header do %>
2
- <%= render Koi::Header::NewComponent.new(model: Admin::User) %>
3
- <% end %>
1
+ <%# locals: (admin_user:) %>
4
2
 
5
- <%= form_with(model: admin, url: admin_admin_users_path) do |form| %>
6
- <%= render "fields", form: %>
3
+ <% content_for(:header) do %>
4
+ <%= breadcrumb_list do %>
5
+ <li><%= link_to("Admin users", admin_admin_users_path) %></li>
6
+ <% end %>
7
7
 
8
- <div class="actions">
9
- <%= form.admin_save %>
10
- </div>
8
+ <h1>New admin user</h1>
11
9
  <% end %>
10
+
11
+ <%= render("form", admin_user:) %>
@@ -1,25 +1,34 @@
1
- <%# locals: (admin:) %>
1
+ <%# locals: (admin_user:) %>
2
2
 
3
- <% content_for :header do %>
4
- <%= render Koi::Header::ShowComponent.new(resource: admin) %>
3
+ <% content_for(:header) do %>
4
+ <%= breadcrumb_list do %>
5
+ <li><%= link_to("Admin users", admin_admin_users_path) %></li>
6
+ <% end %>
7
+
8
+ <h1><%= admin_user %></h1>
9
+
10
+ <%= actions_list do %>
11
+ <li><%= link_to("Edit", edit_admin_admin_user_path(admin_user)) %></li>
12
+ <% end %>
5
13
  <% end %>
6
14
 
7
- <%= render Koi::SummaryTableComponent.new(model: admin) do |builder| %>
8
- <%= builder.text :name %>
9
- <%= builder.text :email %>
10
- <%= builder.date :created_at %>
11
- <%= builder.date :last_sign_in_at, label: "Last sign in" %>
12
- <%= builder.boolean :passkey %>
13
- <%= builder.boolean :otp, label: "MFA" do |otp| %>
15
+ <%= summary_table_with(model: admin_user) do |row| %>
16
+ <% row.text :name %>
17
+ <% row.text :email %>
18
+ <% row.date :created_at %>
19
+ <% row.date(:last_sign_in_at, label: "Last access") %>
20
+ <% row.boolean :passkey %>
21
+ <% row.boolean(:otp, label: "MFA") do |otp| %>
14
22
  <span class="repel">
15
23
  <%= otp %>
16
24
  <% if otp.value %>
17
- <%= button_to("Remove", admin_admin_user_otp_path(admin),
25
+ <%= button_to("Remove", admin_admin_user_otp_path(admin_user),
18
26
  class: "button button--text",
19
27
  method: :delete,
20
28
  form: { data: { turbo_confirm: "Are you sure?" } }) %>
21
29
  <% else %>
22
- <%= kpop_link_to "Add", new_admin_admin_user_otp_path(admin) %>
30
+ <%= link_to("Add", new_admin_admin_user_otp_path(admin_user),
31
+ class: "button", data: { turbo_frame: "edit" }) %>
23
32
  <% end %>
24
33
  </span>
25
34
  <% end %>
@@ -27,7 +36,10 @@
27
36
 
28
37
  <div class="repel">
29
38
  <h3>Passkeys</h3>
30
- <%= kpop_link_to "New passkey", new_admin_admin_user_credential_path(admin), class: "button button--primary" %>
39
+ <%= link_to("New passkey", new_admin_admin_user_credential_path(admin_user),
40
+ class: "button", data: { turbo_frame: "edit" }) %>
31
41
  </div>
32
42
 
33
- <%= render "admin/credentials/credentials", admin: %>
43
+ <%= render "admin/credentials/credentials", admin_user: %>
44
+
45
+ <%= koi_modal_tag("edit") %>
@@ -1,25 +1,27 @@
1
- <%# locals: (admin:) %>
1
+ <%# locals: (admin_user:) %>
2
2
 
3
- <% content_for :header do %>
4
- <%= render Koi::Header::ShowComponent.new(resource: admin) %>
3
+ <% content_for(:header) do %>
4
+ <%= breadcrumb_list do %>
5
+ <li><%= link_to("Admin users", admin_admin_users_path) %></li>
6
+ <% end %>
7
+
8
+ <h1><%= admin_user %></h1>
9
+
10
+ <%= actions_list do %>
11
+ <li><%= link_to("Edit", edit_admin_admin_user_path(admin_user)) %></li>
12
+ <% end %>
5
13
  <% end %>
6
14
 
7
- <%= render Koi::SummaryTableComponent.new(model: admin, class: "item-table") do |builder| %>
8
- <%= builder.text :name %>
9
- <%= builder.text :email %>
10
- <%= builder.date :created_at %>
11
- <%= builder.date :last_sign_in_at, label: "Last sign in" %>
12
- <%= builder.boolean :passkey %>
13
- <%= builder.boolean :otp, label: "MFA" %>
14
- <%= builder.boolean :archived? %>
15
+ <%= summary_table_with(model: admin_user) do |row| %>
16
+ <% row.text :name %>
17
+ <% row.text :email %>
18
+ <% row.date :created_at %>
19
+ <% row.date :last_sign_in_at, label: "Last access" %>
20
+ <% row.boolean :passkey %>
21
+ <% row.boolean :otp, label: "MFA" %>
22
+ <% row.boolean :archived? %>
15
23
  <% end %>
16
24
 
17
- <div class="actions">
18
- <% if admin.archived? %>
19
- <%= button_to "Delete", admin_admin_user_path(admin),
20
- class: "button button--secondary",
21
- method: :delete,
22
- form: { data: { turbo_confirm: "Are you sure?" } } %>
23
- <% end %>
24
- <%= button_to "Generate login link", admin_admin_user_tokens_path(admin), class: "button button--primary", form: { id: "invite" } %>
25
- </div>
25
+ <% unless admin_user.archived? %>
26
+ <%= button_to "Generate login link", admin_admin_user_tokens_path(admin_user), class: "button", form: { id: "invite" } %>
27
+ <% end %>
@@ -1,10 +1,12 @@
1
- <%= table_with(id: dom_id(admin, :credentials), collection: admin.credentials) do |t, c| %>
2
- <% t.text :nickname, label: "Name" %>
3
- <% t.date :updated_at, label: "Last use" do |date| %>
4
- <%= date unless c.created_at == c.updated_at %>
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <%= table_with(id: dom_id(admin_user, :credentials), collection: admin_user.credentials) do |table, credential| %>
4
+ <% table.text :nickname, label: "Name" %>
5
+ <% table.date :updated_at, label: "Last use" do |date| %>
6
+ <%= date unless credential.created_at == credential.updated_at %>
5
7
  <% end %>
6
- <% t.cell :actions, label: "" do %>
7
- <%= link_to("Remove passkey", admin_admin_user_credential_path(admin, c),
8
+ <% table.cell :actions, label: "" do %>
9
+ <%= link_to("Remove passkey", admin_admin_user_credential_path(admin_user, credential),
8
10
  data: { turbo_method: :delete }) %>
9
11
  <% end %>
10
12
  <% end %>
@@ -1,4 +1,6 @@
1
- <%= table_with(id: dom_id(admin, :credentials), collection: admin.credentials) do |t, c| %>
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <%= table_with(id: dom_id(admin_user, :credentials), collection: admin_user.credentials) do |t, c| %>
2
4
  <% t.text :nickname, label: "Name" %>
3
5
  <% t.date :updated_at, label: "Last use" do |date| %>
4
6
  <%= date unless c.created_at == c.updated_at %>
@@ -1,4 +1,5 @@
1
- <%= turbo_stream.kpop.dismiss %>
2
- <%= turbo_stream.replace(dom_id(admin, :credentials)) do %>
3
- <%= render "admin/credentials/credentials", admin: %>
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <%= turbo_stream.replace(dom_id(admin_user, :credentials)) do %>
4
+ <%= render "admin/credentials/credentials", admin_user: %>
4
5
  <% end %>
@@ -1,3 +1,5 @@
1
- <%= turbo_stream.replace(dom_id(admin, :credentials)) do %>
2
- <%= render "admin/credentials/credentials", admin: %>
1
+ <%# locals: (admin_user:) %>
2
+
3
+ <%= turbo_stream.replace(dom_id(admin_user, :credentials)) do %>
4
+ <%= render "admin/credentials/credentials", admin_user: %>
3
5
  <% end %>
@@ -1,37 +1,43 @@
1
- <%= render Kpop::ModalComponent.new(title: "New passkey") do %>
2
- <%= form_with model: admin.credentials.new,
3
- url: admin_admin_user_credentials_path(admin),
4
- class: "flow prose",
5
- data: {
6
- controller: "webauthn-registration",
7
- action: "submit->webauthn-registration#submit",
8
- webauthn_registration_options_value: { publicKey: options },
9
- } do |form| %>
10
- <%= form.hidden_field :response, data: { webauthn_registration_target: "response" } %>
11
- <section class="flow prose" data-webauthn-registration-target="intro">
12
- <p>
13
- Passkeys are secure secrets that are stored by your device.
14
- You will need the device where your passkey is stored to log in.
15
- </p>
16
- <p>
17
- Unlike a password, your password doesn't get sent to the server when you log
18
- in and can't be stolen in a data breach. When you log in with a passkey,
19
- your operating system will prompt you for permission to use the passkey
20
- secret to authenticate the login attempt.
21
- </p>
22
- <p>
23
- We recommend that you store your passkey on your phone or cloud account.
24
- Depending on your browser, you may need to choose "more options" to see
25
- a QR code that you can scan with your phone.
26
- </p>
27
- </section>
28
- <section class="flow" data-webauthn-registration-target="nickname" hidden>
29
- <%= form.govuk_text_field :nickname, label: { text: "Passkey name" } do %>
30
- Enter a name for this passkey to help you distinguish it from other passkeys you may have for this site.
31
- <br>
32
- Example: My Phone, Chrome, iCloud, 1Password
33
- <% end %>
34
- </section>
35
- <%= form.admin_save("Next") %>
36
- <% end %>
1
+ <%# locals: (admin_user:, credential:, options:) %>
2
+
3
+ <%= koi_modal_tag("edit", title: "New passkey") do %>
4
+ <%= koi_modal_header(title: "New passkey", form_id: dom_id(credential, :form)) %>
5
+ <main>
6
+ <%= form_with(model: credential,
7
+ url: admin_admin_user_credentials_path(admin_user),
8
+ id: dom_id(credential, :form),
9
+ class: "flow prose",
10
+ data: {
11
+ controller: "webauthn-registration",
12
+ action: "submit->webauthn-registration#submit",
13
+ webauthn_registration_options_value: { publicKey: options },
14
+ }) do |form| %>
15
+ <%= form.hidden_field :response, data: { webauthn_registration_target: "response" } %>
16
+ <section class="flow prose" data-webauthn-registration-target="intro">
17
+ <p>
18
+ Passkeys are secure secrets that are stored by your device.
19
+ You will need the device where your passkey is stored to log in.
20
+ </p>
21
+ <p>
22
+ Unlike a password, your password doesn't get sent to the server when you log
23
+ in and can't be stolen in a data breach. When you log in with a passkey,
24
+ your operating system will prompt you for permission to use the passkey
25
+ secret to authenticate the login attempt.
26
+ </p>
27
+ <p>
28
+ We recommend that you store your passkey on your phone or cloud account.
29
+ Depending on your browser, you may need to choose "more options" to see
30
+ a QR code that you can scan with your phone.
31
+ </p>
32
+ </section>
33
+ <section class="flow" data-webauthn-registration-target="nickname" hidden>
34
+ <%= form.govuk_text_field :nickname, label: { text: "Passkey name" } do %>
35
+ Enter a name for this passkey to help you distinguish it from other passkeys you may have for this site.
36
+ <br>
37
+ Example: My Phone, Chrome, iCloud, 1Password
38
+ <% end %>
39
+ </section>
40
+ <% end %>
41
+ </main>
42
+ <%= koi_modal_footer("Next", nil, form_id: dom_id(credential, :form)) %>
37
43
  <% end %>
@@ -1 +1,13 @@
1
- <% content_for :title, "Dashboard" %>
1
+ <% content_for :header do %>
2
+ <h1>Dashboard</h1>
3
+ <% end %>
4
+
5
+ <%= navigation_menu_with(
6
+ menu: Koi::Menu.dashboard_menu,
7
+ list: {
8
+ class: "navigation-list | flow",
9
+ role: "list",
10
+ },
11
+ class: "navigation-group | flow",
12
+ role: "list",
13
+ ) %>
@@ -1,8 +1,8 @@
1
- <%# locals: (admin:) %>
1
+ <%# locals: (admin_user:) %>
2
2
 
3
- <%= form_with(id: dom_id(admin, :otp),
4
- model: admin,
5
- url: admin_admin_user_otp_path(admin),
3
+ <%= form_with(id: dom_id(admin_user, :otp),
4
+ model: admin_user,
5
+ url: admin_admin_user_otp_path(admin_user),
6
6
  method: :post,
7
7
  class: "flow") do |form| %>
8
8
  <section class="flow prose">
@@ -15,11 +15,12 @@
15
15
  <ol class="flow">
16
16
  <li>Install an MFA app. Most password managers support MFA.</li>
17
17
  <li>Scan this code using your mobile device or password manager:<br>
18
- <% otp_app_name = t("koi.auth.otp_app_name", host: URI.parse(root_url).host, email: admin.email) %>
19
- <%== RQRCode::QRCode.new(admin.otp.provisioning_uri(otp_app_name)).as_svg(
18
+ <% otp_app_name = t("koi.auth.otp_app_name", host: URI.parse(root_url).host, email: admin_user.email) %>
19
+ <%== RQRCode::QRCode.new(admin_user.otp.provisioning_uri(otp_app_name)).as_svg(
20
20
  color: "000",
21
21
  shape_rendering: "crispEdges",
22
22
  module_size: 3,
23
+ svg_attributes: { class: "qrcode" },
23
24
  use_path: true,
24
25
  ) %>
25
26
  </li>
@@ -28,5 +29,4 @@
28
29
  </section>
29
30
  <%= form.hidden_field :otp_secret %>
30
31
  <%= form.govuk_text_field :token %>
31
- <%= form.admin_save %>
32
32
  <% end %>
@@ -1,5 +1,5 @@
1
- <%# locals: (admin:) %>
1
+ <%# locals: (admin_user:) %>
2
2
 
3
- <%= turbo_stream.replace(dom_id(admin, :otp)) do %>
4
- <%= render("form", admin:) %>
3
+ <%= turbo_stream.replace(dom_id(admin_user, :otp)) do %>
4
+ <%= render("form", admin_user:) %>
5
5
  <% end %>
@@ -1,5 +1,7 @@
1
- <%# locals: (admin:) %>
1
+ <%# locals: (admin_user:) %>
2
2
 
3
- <%= render Kpop::ModalComponent.new(title: "Configure MFA") do %>
4
- <%= render("form", admin:) %>
3
+ <%= koi_modal_tag("edit") do %>
4
+ <%= koi_modal_header(title: "Configure MFA", form_id: dom_id(admin_user, :otp)) %>
5
+ <%= tag.main(render("form", admin_user:)) %>
6
+ <%= koi_modal_footer(form_id: dom_id(admin_user, :otp)) %>
5
7
  <% end %>
@@ -1,9 +1,8 @@
1
1
  <%# locals: (admin_user:) %>
2
2
 
3
- <%= render "layouts/koi/navigation_header" %>
4
-
5
3
  <%= form_with(
6
4
  model: admin_user,
5
+ scope: :admin,
7
6
  url: admin_session_path,
8
7
  data: {
9
8
  controller: "webauthn-authentication",
@@ -24,7 +23,7 @@
24
23
  <%= form.govuk_email_field :email, autofocus: true, autocomplete: "off" %>
25
24
  <%= form.hidden_field :response, data: { webauthn_authentication_target: "response" } %>
26
25
  <%= hidden_field_tag(:redirect, redirect) %>
27
- <div class="actions-group">
26
+ <div class="actions">
28
27
  <%= form.admin_save "Next" %>
29
28
  <%= form.button "🔑", type: :button, class: "button button--secondary", data: { action: "webauthn-authentication#authenticate" } %>
30
29
  </div>
@@ -1,8 +1,6 @@
1
1
  <%# locals: (admin_user:) %>
2
2
 
3
- <%= render "layouts/koi/navigation_header" %>
4
-
5
- <%= form_with(model: admin_user, url: admin_session_path, method: :post) do |form| %>
3
+ <%= form_with(model: admin_user, scope: :admin, url: admin_session_path, method: :post) do |form| %>
6
4
  <%# note, autocomplete off is ignored by browsers but required by PCI-DSS %>
7
5
  <%= form.govuk_text_field :token, autofocus: true, autocomplete: "off" %>
8
6
  <%= hidden_field_tag(:redirect, params[:redirect]) %>
@@ -1,8 +1,6 @@
1
1
  <%# locals: (admin_user:) %>
2
2
 
3
- <%= render "layouts/koi/navigation_header" %>
4
-
5
- <%= form_with(model: admin_user, url: admin_session_path) do |form| %>
3
+ <%= form_with(model: admin_user, scope: :admin, url: admin_session_path) do |form| %>
6
4
  <%= form.hidden_field(:email) %>
7
5
  <%# note, autocomplete off is ignored by browsers but required by PCI-DSS %>
8
6
  <%= form.govuk_password_field :password, autofocus: true, autocomplete: "off" %>
@@ -1,14 +1,12 @@
1
1
  <%# locals: (admin_user:, token:) %>
2
2
 
3
- <%= render "layouts/koi/navigation_header" %>
4
-
5
3
  <%= form_with(url: admin_session_token_path(token), method: :patch) do |form| %>
6
4
  <p>Welcome to Koi Admin</p>
7
- <%= render Koi::SummaryListComponent.new(model: admin_user, class: "item-table") do |builder| %>
8
- <%= builder.text :name %>
9
- <%= builder.text :email %>
5
+ <%= summary_table_with(model: admin_user) do |row| %>
6
+ <%= row.text :name %>
7
+ <%= row.text :email %>
10
8
  <% end %>
11
- <div class="actions-group">
9
+ <div class="actions">
12
10
  <%= form.admin_save "Sign in" %>
13
11
  </div>
14
12
  <% end %>
@@ -0,0 +1,9 @@
1
+ <%# locals: (url_rewrite:) %>
2
+
3
+ <%= form_with(model: url_rewrite) do |form| %>
4
+ <%= form.govuk_text_field(:from, hint: { text: "Should begin with /" }) %>
5
+ <%= form.govuk_text_field :to %>
6
+ <%= form.govuk_check_box_field :active %>
7
+ <%= form.govuk_enum_select :status_code %>
8
+ <%= form.button(type: :submit, class: "button") %>
9
+ <% end %>