katalyst-koi 4.5.0.beta.2 → 4.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/koi/admin.css +201 -108
  3. data/app/assets/builds/koi/admin.css.map +1 -1
  4. data/app/assets/javascripts/koi/controllers/clipboard_controller.js +23 -0
  5. data/app/assets/stylesheets/koi/components/_clipboard.scss +46 -0
  6. data/app/assets/stylesheets/koi/components/_index-table.scss +54 -3
  7. data/app/assets/stylesheets/koi/components/_index.scss +1 -0
  8. data/app/assets/stylesheets/koi/layouts/_navigation.scss +0 -1
  9. data/app/assets/stylesheets/koi/pages/_login.scss +6 -0
  10. data/app/assets/stylesheets/koi/themes/_content.scss +38 -0
  11. data/app/components/koi/content/editor/item_form_component.html.erb +1 -1
  12. data/app/components/koi/content/editor/item_form_component.rb +4 -2
  13. data/app/components/koi/summary_list/attachment_component.rb +11 -1
  14. data/app/components/koi/summary_list_component.rb +2 -2
  15. data/app/components/koi/tables/body.rb +116 -27
  16. data/app/components/koi/tables/body_row_component.rb +116 -18
  17. data/app/components/koi/tables/header.rb +51 -1
  18. data/app/components/koi/tables/header_cell_component.rb +30 -0
  19. data/app/components/koi/tables/header_row_component.rb +182 -20
  20. data/app/controllers/admin/tokens_controller.rb +60 -0
  21. data/app/controllers/concerns/koi/controller/json_web_token.rb +22 -0
  22. data/app/helpers/koi/date_helper.rb +18 -28
  23. data/app/models/admin/user.rb +2 -1
  24. data/app/views/admin/admin_users/show.html.erb +7 -6
  25. data/app/views/admin/sessions/new.html.erb +9 -0
  26. data/app/views/admin/tokens/create.turbo_stream.erb +9 -0
  27. data/app/views/admin/tokens/show.html.erb +13 -0
  28. data/app/views/katalyst/content/tables/_table.html+form.erb +41 -0
  29. data/app/views/layouts/koi/_navigation.html.erb +1 -1
  30. data/app/views/layouts/koi/_navigation_header.html.erb +6 -1
  31. data/config/routes.rb +8 -1
  32. data/lib/generators/koi/admin_controller/templates/controller.rb.tt +6 -7
  33. data/lib/generators/koi/admin_controller/templates/controller_spec.rb.tt +4 -4
  34. data/lib/generators/koi/admin_views/admin_views_generator.rb +6 -4
  35. data/lib/koi/config.rb +3 -0
  36. data/lib/koi/form_builder.rb +2 -2
  37. metadata +11 -4
@@ -5,46 +5,208 @@ module Koi
5
5
  # Custom header row component, in order to override the default header cell component
6
6
  # for number columns, we add a class to the header cell to allow for custom styling
7
7
  class HeaderRowComponent < Katalyst::Tables::HeaderRowComponent
8
- def boolean(attribute, **attributes, &block)
9
- header_cell(attribute, **attributes, &block)
8
+ # Renders a boolean column header
9
+ # @param method [Symbol] the method to call on the record to get the value
10
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
11
+ # @option attributes [String] :label (nil) The label options to display in the header
12
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
13
+ # @option attributes [String] :width (:xs) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
14
+ #
15
+ # @example Render a boolean column header
16
+ # <% row.boolean :active %> # => <th>Active</th>
17
+ #
18
+ # @example Render a boolean column header with a custom label
19
+ # <% row.boolean :active, label: "Published" %> # => <th>Published</th>
20
+ #
21
+ # @example Render a boolean column header with medium width
22
+ # <% row.boolean :active, width: :m %>
23
+ # # => <th class="width-s">Active</th>
24
+ #
25
+ # @see Koi::Tables::BodyRowComponent#boolean
26
+ def boolean(method, **attributes, &block)
27
+ header_cell(method, component: Header::BooleanComponent, **attributes, &block)
10
28
  end
11
29
 
12
- def date(attribute, **attributes, &block)
13
- header_cell(attribute, **attributes, &block)
30
+ # Renders a date column header
31
+ # @param method [Symbol] the method to call on the record to get the value
32
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
33
+ # @option attributes [String] :label (nil) The label options to display in the header
34
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
35
+ # @option attributes [String] :width (:s) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
36
+ #
37
+ # @example Render a date column header
38
+ # <% row.date :published_on %> # => <th>Published on</th>
39
+ #
40
+ # @example Render a date column header with a custom label
41
+ # <% row.date :published_on, label: "Date" %> # => <th>Date</th>
42
+ #
43
+ # @example Render a date column header with small width
44
+ # <% row.date :published_on, width: :s %>
45
+ # # => <th class="width-s">Published on</th>
46
+ #
47
+ # @see Koi::Tables::BodyRowComponent#date
48
+ def date(method, **attributes, &block)
49
+ header_cell(method, component: Header::DateComponent, **attributes, &block)
14
50
  end
15
51
 
16
- def datetime(attribute, **attributes, &block)
17
- header_cell(attribute, **attributes, &block)
52
+ # Renders a datetime column header
53
+ # @param method [Symbol] the method to call on the record to get the value
54
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
55
+ # @option attributes [String] :label (nil) The label options to display in the header
56
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
57
+ # @option attributes [String] :width (:m) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
58
+ #
59
+ # @example Render a datetime column header
60
+ # <% row.datetime :created_at %> # => <th>Created at</th>
61
+ #
62
+ # @example Render a datetime column header with a custom label
63
+ # <% row.datetime :created_at, label: "Published at" %> # => <th>Published at</th>
64
+ #
65
+ # @example Render a datetime column header with small width
66
+ # <% row.datetime :created_at, width: :s %>
67
+ # # => <th class="width-s">Created at</th>
68
+ #
69
+ # @see Koi::Tables::BodyRowComponent#datetime
70
+ def datetime(method, **attributes, &block)
71
+ header_cell(method, component: Header::DateTimeComponent, **attributes, &block)
18
72
  end
19
73
 
20
- def number(attribute, **attributes, &block)
21
- header_cell(attribute, **attributes, component: Header::NumberComponent, &block)
74
+ # Renders a number column header
75
+ # @param method [Symbol] the method to call on the record to get the value
76
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
77
+ # @option attributes [String] :label (nil) The label options to display in the header
78
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
79
+ # @option attributes [String] :width (:xs) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
80
+ #
81
+ # @example Render a number column header
82
+ # <% row.number :comment_count %> # => <th>Comments</th>
83
+ #
84
+ # @example Render a number column header with a custom label
85
+ # <% row.number :comment_count, label: "Comments" %> # => <th>Comments</th>
86
+ #
87
+ # @example Render a number column header with medium width
88
+ # <% row.number :comment_count, width: :m %>
89
+ # # => <th class="width-m">Comment Count</th>
90
+ #
91
+ # @see Koi::Tables::BodyRowComponent#number
92
+ def number(method, **attributes, &block)
93
+ header_cell(method, component: Header::NumberComponent, **attributes, &block)
22
94
  end
23
95
 
24
- def money(attribute, **attributes, &block)
25
- header_cell(attribute, **attributes, component: Header::NumberComponent, &block)
96
+ # Renders a currency column header
97
+ # @param method [Symbol] the method to call on the record to get the value
98
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
99
+ # @option attributes [String] :label (nil) The label options to display in the header
100
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
101
+ # @option attributes [String] :width (:s) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
102
+ #
103
+ # @example Render a currency column header
104
+ # <% row.currency :price %> # => <th>Price</th>
105
+ #
106
+ # @example Render a currency column header with a custom label
107
+ # <% row.currency :price, label: "Amount($)" %> # => <th>Amount($)</th>
108
+ #
109
+ # @example Render a currency column header with medium width
110
+ # <% row.currency :price, width: :m %>
111
+ # # => <th class="width-m">Price</th>
112
+ #
113
+ # @see Koi::Tables::BodyRowComponent#currency
114
+ def currency(method, **attributes, &block)
115
+ header_cell(method, component: Header::CurrencyComponent, **attributes, &block)
26
116
  end
27
117
 
28
- def rich_text(attribute, **attributes, &block)
29
- header_cell(attribute, **attributes, &block)
118
+ # Renders a rich text column header
119
+ # @param method [Symbol] the method to call on the record to get the value
120
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
121
+ # @option attributes [String] :label (nil) The label options to display in the header
122
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
123
+ # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
124
+ #
125
+ # @example Render a rich text column header
126
+ # <% row.rich_text :content %> # => <th>Content</th>
127
+ #
128
+ # @example Render a rich text column header with a custom label
129
+ # <% row.rich_text :content, label: "Post content" %> # => <th>Post content</th>
130
+ #
131
+ # @example Render a rich text column header with large width
132
+ # <% row.rich_text :content, width: :l %>
133
+ # # => <th class="width-l">Content</th>
134
+ #
135
+ # @see Koi::Tables::BodyRowComponent#rich_text
136
+ def rich_text(method, **attributes, &block)
137
+ header_cell(method, component: Header::TextComponent, **attributes, &block)
30
138
  end
31
139
 
32
- def link(attribute, **attributes, &block)
33
- header_cell(attribute, **attributes, &block)
140
+ # Renders a link column header
141
+ # @param method [Symbol] the method to call on the record to get the value
142
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
143
+ # @option attributes [String] :label (nil) The label options to display in the header
144
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
145
+ # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
146
+ #
147
+ # @example Render a link column header
148
+ # <% row.link :link %> # => <th>Link</th>
149
+ #
150
+ # @example Render a link column header with a custom label
151
+ # <% row.link :link, label: "Post" %> # => <th>Post</th>
152
+ #
153
+ # @example Render a link column header with small width
154
+ # <% row.link :content, width: :s %>
155
+ # # => <th class="width-s">Content</th>
156
+ #
157
+ # @see Koi::Tables::BodyRowComponent#link
158
+ def link(method, **attributes, &block)
159
+ header_cell(method, component: Header::LinkComponent, **attributes, &block)
34
160
  end
35
161
 
36
- def text(attribute, **attributes, &block)
37
- header_cell(attribute, **attributes, &block)
162
+ # Renders a text column header
163
+ # @param method [Symbol] the method to call on the record to get the value
164
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
165
+ # @option attributes [String] :label (nil) The label options to display in the header
166
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
167
+ # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
168
+ #
169
+ # @example Render a text column header
170
+ # <% row.text :content %> # => <th>Content</th>
171
+ #
172
+ # @example Render a text column header with a custom label
173
+ # <% row.text :content, label: "Description" %> # => <th>Description</th>
174
+ #
175
+ # @example Render a text column header with large width
176
+ # <% row.text :content, width: :l %>
177
+ # # => <th class="width-l">Content</th>
178
+ #
179
+ # @see Koi::Tables::BodyRowComponent#text
180
+ def text(method, **attributes, &block)
181
+ header_cell(method, component: Header::TextComponent, **attributes, &block)
38
182
  end
39
183
 
40
- def image(attribute, **attributes, &block)
41
- header_cell(attribute, **attributes, &block)
184
+ # Renders a attachment column header
185
+ # @param method [Symbol] the method to call on the record to get the value
186
+ # @param attributes [Hash] additional arguments are applied as html attributes to the th element
187
+ # @option attributes [String] :label (nil) The label options to display in the header
188
+ # @option attributes [Hash] :link ({}) The link options for the sorting link
189
+ # @option attributes [String] :width (nil) The width of the column, can be +:xs+, +:s+, +:m+, +:l+ or nil
190
+ #
191
+ # @example Render a attachment column header
192
+ # <% row.attachment :attachment %> # => <th>Attachment</th>
193
+ #
194
+ # @example Render a attachment column header with a custom label
195
+ # <% row.attachment :attachment, label: "Document" %> # => <th>Document</th>
196
+ #
197
+ # @example Render a attachment column header with small width
198
+ # <% row.attachment :attachment, width: :s %>
199
+ # # => <th class="width-s">Attachment</th>
200
+ #
201
+ # @see Koi::Tables::BodyRowComponent#attachment
202
+ def attachment(method, **attributes, &block)
203
+ header_cell(method, component: Header::AttachmentComponent, **attributes, &block)
42
204
  end
43
205
 
44
206
  private
45
207
 
46
- def header_cell(attribute, component: HeaderCellComponent, **attributes, &block)
47
- with_column(component.new(@table, attribute, link: @link_attributes, **attributes), &block)
208
+ def header_cell(method, component: HeaderCellComponent, **attributes, &block)
209
+ with_column(component.new(@table, method, link: @link_attributes, **attributes), &block)
48
210
  end
49
211
  end
50
212
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class TokensController < ApplicationController
5
+ include Koi::Controller::JsonWebToken
6
+
7
+ skip_before_action :authenticate_admin, only: %i[show update]
8
+ before_action :set_token, only: %i[show update]
9
+
10
+ def create
11
+ admin = Admin::User.find(params[:id])
12
+ token = encode_token(admin_id: admin.id, exp: 5.minutes.from_now.to_i, iat: Time.now.to_i)
13
+
14
+ render locals: { token: }
15
+ end
16
+
17
+ def update
18
+ return redirect_to admin_dashboard_path, status: :see_other if admin_signed_in?
19
+
20
+ return redirect_to new_admin_session_path, status: :see_other, notice: "invalid token" if @token.blank?
21
+
22
+ admin = Admin::User.find(@token[:admin_id])
23
+ sign_in_admin(admin)
24
+
25
+ redirect_to admin_admin_user_path(admin)
26
+ end
27
+
28
+ def show
29
+ return redirect_to new_admin_session_path, notice: "Token invalid or consumed already" if @token.blank?
30
+
31
+ admin = Admin::User.find(@token[:admin_id])
32
+
33
+ if token_utilised?(admin, @token)
34
+ return redirect_to new_admin_session_path, notice: "Token invalid or consumed already"
35
+ end
36
+
37
+ render locals: { admin:, token: params[:token] }, layout: "koi/login"
38
+ end
39
+
40
+ private
41
+
42
+ def set_token
43
+ @token = decode_token(params[:token])
44
+ end
45
+
46
+ def token_utilised?(admin, token)
47
+ admin.current_sign_in_at.present? || (admin.last_sign_in_at.present? && admin.last_sign_in_at.to_i > token[:iat])
48
+ end
49
+
50
+ def sign_in_admin(admin)
51
+ admin.current_sign_in_at = Time.current
52
+ admin.current_sign_in_ip = request.remote_ip
53
+ admin.sign_in_count = 1
54
+
55
+ # disable validations to allow saving without password or passkey credentials
56
+ admin.save!(validate: false)
57
+ session[:admin_user_id] = admin.id
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Koi
4
+ module Controller
5
+ module JsonWebToken
6
+ extend ActiveSupport::Concern
7
+
8
+ SECRET_KEY = Rails.application.secret_key_base
9
+
10
+ def encode_token(**payload)
11
+ JWT.encode(payload, SECRET_KEY)
12
+ end
13
+
14
+ def decode_token(token)
15
+ payload = JWT.decode(token, SECRET_KEY)[0]
16
+ HashWithIndifferentAccess.new(payload)
17
+ rescue JWT::DecodeError
18
+ nil
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,36 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Naming/MethodName
4
3
  module Koi
5
4
  module DateHelper
6
- # @deprecated
7
- def date_format(date, format)
8
- date.strftime format.gsub(/yyyy/, "%Y")
9
- .gsub(/yy/, "%y")
10
- .gsub(/Month/, "%B")
11
- .gsub(/M/, "%b")
12
- .gsub(/mm/, "%m")
13
- .gsub(/m/, "%-m")
14
- .gsub(/Day/, "%A")
15
- .gsub(/D/, "%a")
16
- .gsub(/dd/, "%d")
17
- .gsub(/d/, "%-d")
18
- end
19
-
20
- # @deprecated
21
- def date_Month_d_yyyy(date)
22
- date.strftime "%B %-d, %Y"
23
- end
24
-
25
- # @deprecated
26
- def date_d_Month_yyyy(date)
27
- date.strftime "%-d %B %Y"
28
- end
5
+ # Returns a string representing the number of days ago or from now.
6
+ # If the date is not 'recent' returns nil.
7
+ def days_ago_in_words(value)
8
+ from_time = value.to_time
9
+ to_time = Date.current.to_time
10
+ distance_in_days = ((to_time - from_time) / (24.0 * 60.0 * 60.0)).round
29
11
 
30
- # @deprecated
31
- def date_d_M_yy(date)
32
- date.strftime "%-d %b %y"
12
+ case distance_in_days
13
+ when 0
14
+ "today"
15
+ when 1
16
+ "yesterday"
17
+ when -1
18
+ "tomorrow"
19
+ when 2..5
20
+ "#{distance_in_days} days ago"
21
+ when -5..-2
22
+ "#{distance_in_days.abs} days from now"
23
+ end
33
24
  end
34
25
  end
35
26
  end
36
- # rubocop:enable Naming/MethodName
@@ -8,7 +8,8 @@ module Admin
8
8
  ActiveModel::Name.new(self, nil, "Admin")
9
9
  end
10
10
 
11
- has_secure_password :password
11
+ # disable validations for password_digest
12
+ has_secure_password validations: false
12
13
 
13
14
  has_many :credentials, inverse_of: :admin, class_name: "Admin::Credential", dependent: :destroy
14
15
 
@@ -2,12 +2,12 @@
2
2
  <%= render Koi::Header::ShowComponent.new(resource: admin) %>
3
3
  <% end %>
4
4
 
5
- <%= definition_list(class: "item-table") do |builder| %>
6
- <%= builder.item admin, :name %>
7
- <%= builder.item admin, :email %>
8
- <%= builder.item admin, :created_at %>
9
- <%= builder.item admin, :last_sign_in_at, label: { text: "Last sign in" } %>
10
- <%= builder.item admin, :archived? %>
5
+ <%= render Koi::SummaryListComponent.new(model: admin, class: "item-table") do |builder| %>
6
+ <%= builder.text :name %>
7
+ <%= builder.text :email %>
8
+ <%= builder.datetime :created_at %>
9
+ <%= builder.datetime :last_sign_in_at, label: { text: "Last sign in" } %>
10
+ <%= builder.boolean :archived? %>
11
11
  <% end %>
12
12
 
13
13
  <div class="actions">
@@ -17,6 +17,7 @@
17
17
  method: :delete,
18
18
  form: { data: { turbo_confirm: "Are you sure?" } } %>
19
19
  <% end %>
20
+ <%= button_to "Generate login link", invite_admin_admin_user_path(admin), class: "button button--primary", form: { id: "invite" } %>
20
21
  </div>
21
22
 
22
23
  <h2>Authentication</h2>
@@ -7,6 +7,15 @@
7
7
  webauthn_authentication_options_value: { publicKey: webauthn_auth_options },
8
8
  },
9
9
  ) do |f| %>
10
+ <% unless flash.empty? %>
11
+ <div class="govuk-error-summary">
12
+ <ul class="govuk-error-summary__list">
13
+ <% flash.each do |_, message| %>
14
+ <%= tag.li message %>
15
+ <% end %>
16
+ </ul>
17
+ </div>
18
+ <% end %>
10
19
  <%= f.govuk_fieldset legend: nil do %>
11
20
  <%= f.govuk_email_field :email, autofocus: true, autocomplete: "email" %>
12
21
  <%= f.govuk_password_field :password, autocomplete: "current-password" %>
@@ -0,0 +1,9 @@
1
+ <%= turbo_stream.replace "invite" do %>
2
+ <div class="action copy-to-clipboard govuk-input__wrapper" data-controller="clipboard" data-clipboard-supported-class="clipboard--supported">
3
+ <%= text_field_tag :invite_link, admin_token_url(token), readonly: true, data: { clipboard_target: "source" } %>
4
+ <button class="govuk-input__suffix clipboard-button" aria-hidden="true" data-action="clipboard#copy">
5
+ Copy link
6
+ </button>
7
+ <div class="copy-to-clipboard-feedback" role="alert"></div>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= render "layouts/koi/navigation_header" %>
2
+
3
+ <%= form_with(url: accept_admin_session_path) do |form| %>
4
+ <%= tag.input name: :token, type: :hidden, value: token %>
5
+ <p>Welcome to Koi Admin</p>
6
+ <%= render Koi::SummaryListComponent.new(model: admin, class: "item-table") do |builder| %>
7
+ <%= builder.text :name %>
8
+ <%= builder.text :email %>
9
+ <% end %>
10
+ <div class="actions-group">
11
+ <%= form.admin_save "Sign in" %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <%= render Koi::Content::Editor::ItemFormComponent.new(model: table, url: path, data: { controller: "content--editor--table" }) do |form| %>
2
+ <%= form.content_heading_fieldset %>
3
+
4
+ <div class="govuk-form-group">
5
+ <label for="item-content-field" class="govuk-label govuk-label--s">Content</label>
6
+ <% content = sanitize_content_table(normalize_content_table(form.object, heading: false)) %>
7
+ <div class="govuk-textarea content--editor--table-editor"
8
+ contenteditable="true"
9
+ data-content--editor--table-target="content"
10
+ data-action="paste->content--editor--table#paste"
11
+ id="item-content-field">
12
+ <%= content %>
13
+ </div>
14
+ <%= form.hidden_field :content, value: content, data: { content__editor__table_target: "input" } %>
15
+ </div>
16
+
17
+ <%# hidden button to receive <Enter> events (HTML-default is to click first button in form) %>
18
+ <%= form.button "Save", hidden: "" %>
19
+
20
+ <%# hidden button to submit the table for re-rendering %>
21
+ <%= form.button "Update",
22
+ formaction: table.persisted? ? content_routes.table_path : content_routes.tables_path,
23
+ hidden: "",
24
+ data: { content__editor__table_target: "update" } %>
25
+
26
+ <%= form.govuk_number_field :heading_rows,
27
+ label: { text: "Heading rows" },
28
+ width: 2,
29
+ placeholder: 0,
30
+ min: 0,
31
+ data: { content__editor__table_target: "headerRows",
32
+ action: "input->content--editor--table#update" } %>
33
+
34
+ <%= form.govuk_number_field :heading_columns,
35
+ label: { text: "Heading columns" },
36
+ width: 2,
37
+ placeholder: 0,
38
+ min: 0,
39
+ data: { content__editor__table_target: "headerColumns",
40
+ action: "input->content--editor--table#update" } %>
41
+ <% end %>
@@ -2,7 +2,7 @@
2
2
  data-action="shortcut:go@document->navigation#focus
3
3
  navigation:toggle@document->navigation#toggle
4
4
  shortcut:nav-toggle@document->navigation#toggle">
5
- <%= render "layouts/koi/navigation_header" %>
5
+ <%= render "layouts/koi/navigation_header", admin: current_admin %>
6
6
  <div class="filter">
7
7
  <input type="search" placeholder="Filter menu" autocomplete="off"
8
8
  data-navigation-target="filter"
@@ -1,6 +1,11 @@
1
1
  <header>
2
2
  <h2 class="site-name"><%= URI.parse(root_url).host %></h2>
3
- <h3 class="admin-name">Koi Admin</h3>
3
+ <%# show username on nav header and Koi Admin on login page %>
4
+ <% if local_assigns[:admin].present? %>
5
+ <%= link_to admin.name, admin_admin_user_path(admin), class: "admin-name" %>
6
+ <% else %>
7
+ <h3 class="admin-name">Koi Admin</h3>
8
+ <% end %>
4
9
  <%# default, prefer using an icon in a Koi override %>
5
10
  <span class="site-icon"><%= URI.parse(root_url).host[0] %></span>
6
11
  </header>
data/config/routes.rb CHANGED
@@ -2,13 +2,20 @@
2
2
 
3
3
  Rails.application.routes.draw do
4
4
  namespace :admin do
5
- resource :session, only: %i[new create destroy]
5
+ resource :session, only: %i[new create destroy] do
6
+ post :accept, to: "tokens#update"
7
+ end
6
8
 
7
9
  resources :url_rewrites
8
10
  resources :admin_users do
9
11
  resources :credentials, only: %i[new create destroy]
12
+ post :invite, on: :member, to: "tokens#create"
10
13
  end
11
14
 
15
+ # JWT tokens have dots(represents the 3 parts of data) in them, so we need to allow them in the URL
16
+ # can by pass if we use token as a query param
17
+ get "token/:token", to: "tokens#show", as: :token, token: /[^\/]+/
18
+
12
19
  resource :cache, only: %i[destroy]
13
20
  resource :dashboard, only: %i[show]
14
21
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Admin
4
4
  class <%= controller_class_name %>Controller < ApplicationController
5
-
6
5
  before_action :set_<%= singular_name %>, only: %i[show edit update destroy]
7
6
 
8
7
  def index
@@ -31,7 +30,7 @@ module Admin
31
30
  @<%= singular_name %> = ::<%= class_name %>.new(<%= singular_table_name %>_params)
32
31
 
33
32
  if @<%= singular_name %>.save
34
- redirect_to [:admin, @<%= singular_name %>]
33
+ redirect_to [:admin, @<%= singular_name %>], status: :see_other
35
34
  else
36
35
  render :new, locals: { <%= singular_name %>: @<%= singular_name %> }, status: :unprocessable_entity
37
36
  end
@@ -39,16 +38,16 @@ module Admin
39
38
 
40
39
  def update
41
40
  if @<%= singular_name %>.update(<%= singular_table_name %>_params)
42
- redirect_to action: :show
41
+ redirect_to action: :show, status: :see_other
43
42
  else
44
43
  render :edit, locals: { <%= singular_name %>: @<%= singular_name %> }, status: :unprocessable_entity
45
44
  end
46
45
  end
47
46
 
48
47
  def destroy
49
- @<%= singular_name %>.destroy
48
+ @<%= singular_name %>.destroy!
50
49
 
51
- redirect_to action: :index
50
+ redirect_to action: :index, status: :see_other
52
51
  end
53
52
 
54
53
  private
@@ -56,9 +55,9 @@ module Admin
56
55
  # Only allow a list of trusted parameters through.
57
56
  def <%= "#{singular_table_name}_params" %>
58
57
  <%- if attributes_names.empty? -%>
59
- params.fetch(:<%= singular_table_name %>, {})
58
+ params.fetch(:<%= singular_table_name %>, {})
60
59
  <%- else -%>
61
- params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
60
+ params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
62
61
  <%- end -%>
63
62
  end
64
63
 
@@ -23,7 +23,7 @@ RSpec.describe Admin::<%= controller_class_name %>Controller do
23
23
 
24
24
  it "paginates the collection" do
25
25
  action
26
- expect(response.body).to have_selector("tbody tr", count: 20)
26
+ expect(response.body).to have_css("tbody tr", count: 20)
27
27
  end
28
28
  end
29
29
 
@@ -37,7 +37,7 @@ RSpec.describe Admin::<%= controller_class_name %>Controller do
37
37
 
38
38
  it "finds first in second place" do
39
39
  action
40
- expect(response.body).to have_selector("tbody tr + tr td", text: "first")
40
+ expect(response.body).to have_css("tbody tr + tr td", text: "first")
41
41
  end
42
42
  end
43
43
 
@@ -51,12 +51,12 @@ RSpec.describe Admin::<%= controller_class_name %>Controller do
51
51
 
52
52
  it "finds the needle" do
53
53
  action
54
- expect(response.body).to have_selector("table td", text: "first")
54
+ expect(response.body).to have_css("table td", text: "first")
55
55
  end
56
56
 
57
57
  it "removes the chaff" do
58
58
  action
59
- expect(response.body).not_to have_selector("table td", text: "second")
59
+ expect(response.body).to have_no_css("table td", text: "second")
60
60
  end
61
61
  end
62
62
  end
@@ -38,10 +38,10 @@ module Koi
38
38
  %(<%= form.govuk_check_box_field :#{attribute.name} %>)
39
39
  when :date
40
40
  %(<%= form.govuk_date_field :#{attribute.name}, legend: { size: "s" } %>)
41
- when :text
42
- %(<%= form.govuk_rich_text_area :#{attribute.name} %>)
43
- when :rich_text
41
+ when :rich_text, :text
44
42
  %(<%= form.govuk_rich_text_area :#{attribute.name} %>)
43
+ when :attachment
44
+ %(<%= form.govuk_image_field :#{attribute.name} %>)
45
45
  else
46
46
  ""
47
47
  end
@@ -61,6 +61,8 @@ module Koi
61
61
  %(<% dl.datetime :#{attribute.name} %>)
62
62
  when :rich_text
63
63
  %(<% dl.rich_text :#{attribute.name} %>)
64
+ when :attachment
65
+ %(<% dl.attachment :#{attribute.name} %>)
64
66
  else
65
67
  %(<% dl.text :#{attribute.name} %>)
66
68
  end
@@ -79,7 +81,7 @@ module Koi
79
81
  when :rich_text
80
82
  %(<% row.rich_text :#{attribute.name} %>)
81
83
  when :attachment
82
- %(<% row.image :#{attribute.name} %>)
84
+ %(<% row.attachment :#{attribute.name} %>)
83
85
  else
84
86
  %(<% row.text :#{attribute.name} %>)
85
87
  end