bullet_train 1.2.9 → 1.2.11

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/account/two_factors_controller.rb +21 -5
  3. data/app/controllers/turbo_devise_controller.rb +1 -1
  4. data/app/helpers/account/forms_helper.rb +3 -1
  5. data/app/helpers/attributes_helper.rb +2 -2
  6. data/app/javascript/controllers/clipboard_controller.js +1 -1
  7. data/app/javascript/controllers/connection_workflow_controller.js +7 -0
  8. data/app/javascript/controllers/desktop_menu_controller.js +26 -0
  9. data/app/javascript/controllers/index.js +4 -0
  10. data/app/javascript/index.js +2 -1
  11. data/app/javascript/support/turn.js +183 -0
  12. data/app/models/billing/mock_limiter.rb +5 -1
  13. data/app/models/concerns/records/base.rb +8 -9
  14. data/app/models/concerns/users/base.rb +2 -2
  15. data/app/views/account/invitations/_form.html.erb +2 -2
  16. data/app/views/account/memberships/_form.html.erb +2 -2
  17. data/app/views/account/memberships/_membership.html.erb +1 -1
  18. data/app/views/account/teams/_breadcrumbs.html.erb +12 -5
  19. data/app/views/account/teams/_team.html.erb +4 -4
  20. data/app/views/account/two_factors/verify.js.erb +1 -0
  21. data/app/views/devise/passwords/edit.html.erb +1 -1
  22. data/app/views/devise/registrations/_two_factor.html.erb +29 -8
  23. data/app/views/devise/sessions/new.html.erb +4 -2
  24. data/app/views/layouts/docs.html.erb +7 -7
  25. data/config/locales/en/teams.en.yml +2 -0
  26. data/config/locales/en/users.en.yml +8 -1
  27. data/config/routes.rb +3 -1
  28. data/docs/action-models.md +5 -5
  29. data/docs/api/versioning.md +0 -2
  30. data/docs/api.md +4 -4
  31. data/docs/application-options.md +1 -1
  32. data/docs/billing/usage.md +94 -16
  33. data/docs/field-partials.md +20 -20
  34. data/docs/font-awesome-pro.md +1 -1
  35. data/docs/getting-started.md +3 -3
  36. data/docs/i18n.md +3 -3
  37. data/docs/indirection.md +6 -4
  38. data/docs/namespacing.md +1 -1
  39. data/docs/onboarding.md +8 -8
  40. data/docs/overriding.md +1 -1
  41. data/docs/permissions.md +1 -1
  42. data/docs/seeds.md +1 -1
  43. data/docs/testing.md +2 -1
  44. data/docs/themes.md +18 -11
  45. data/docs/tunneling.md +2 -2
  46. data/docs/upgrades.md +2 -1
  47. data/lib/bullet_train/configuration.rb +15 -0
  48. data/lib/bullet_train/engine.rb +15 -0
  49. data/lib/bullet_train/version.rb +1 -1
  50. data/lib/bullet_train.rb +11 -1
  51. data/lib/colorizer.rb +1 -1
  52. data/lib/tasks/bullet_train_tasks.rake +66 -55
  53. metadata +22 -9
  54. data/app/views/account/invitations/_invitation.json.jbuilder +0 -7
  55. data/app/views/account/invitations/index.json.jbuilder +0 -1
  56. data/app/views/account/invitations/show.json.jbuilder +0 -1
  57. data/app/views/account/teams/_team.json.jbuilder +0 -9
  58. data/app/views/account/teams/index.json.jbuilder +0 -1
  59. data/app/views/account/teams/show.json.jbuilder +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a78a3f3d80b6f4d1dd5207eeff10c028f551e4d6a2f2b237091941f9b9640d67
4
- data.tar.gz: 3c6cc925170d0f71ed3e9c48336839d834ca809b02162f7a528369a787c2443e
3
+ metadata.gz: 7d28fd8d3f1e1ce07073cc4aadfd04758fc3a13aab3723577508be2055269211
4
+ data.tar.gz: ee5a557dd99c80d925c6f6dcb3c6c4978a71a27ac295b700bb27b854194166b0
5
5
  SHA512:
6
- metadata.gz: 115cb038043cb425ab9e955615373921c6faa22930505656ccd2b6e20b9c4e11a73bbb4d0d696d1f117808ac6070e17a900de5f0132b33c3139e681b99de48a9
7
- data.tar.gz: 8203f656d96ba1f4ffaacfcfe70c848f757d5a6d92589819115738b0145a3a627d0cd2069ce504a69dd62cac315df91e7ef8bea9844114d04ed508e84bb0f4dd
6
+ metadata.gz: d4bf3b8d872805ffbbc916056943eff7999acc6d614b214a4f62cf89633fe487854b659c2aacac24e1dcf39b7c6a6176e7a79999f7be6cb4f2e2574e515b6aa5
7
+ data.tar.gz: f7e25e0b2eb89f1af4772e22b4752d0a9d8025b042c78a9b468d8a6c00fea4c08eec8c5c9ac4e51cddd9d91517cb9db5a0bb593b2b81b3eb746ed61bf51b1bc2
@@ -1,18 +1,34 @@
1
1
  class Account::TwoFactorsController < Account::ApplicationController
2
2
  before_action :authenticate_user!
3
3
 
4
+ def verify
5
+ @user = current_user
6
+
7
+ otp_code = params["user"]["otp_attempt"]
8
+ @verified = current_user.validate_and_consume_otp!(otp_code)
9
+
10
+ if @verified
11
+ current_user.update(otp_required_for_login: true)
12
+ else
13
+ current_user.update(
14
+ otp_required_for_login: false,
15
+ otp_secret: nil
16
+ )
17
+ end
18
+ end
19
+
4
20
  def create
5
21
  @backup_codes = current_user.generate_otp_backup_codes!
6
22
  @user = current_user
7
23
 
8
- current_user.update(
9
- otp_secret: User.generate_otp_secret,
10
- otp_required_for_login: true
11
- )
24
+ current_user.update(otp_secret: User.generate_otp_secret)
12
25
  end
13
26
 
14
27
  def destroy
15
28
  @user = current_user
16
- current_user.update(otp_required_for_login: false)
29
+ current_user.update(
30
+ otp_required_for_login: false,
31
+ otp_secret: nil
32
+ )
17
33
  end
18
34
  end
@@ -7,7 +7,7 @@ class TurboDeviseController < ApplicationController
7
7
  if get?
8
8
  raise error
9
9
  elsif has_errors? && default_action
10
- render rendering_options.merge(formats: :html, status: :unprocessable_entity)
10
+ render error_rendering_options.merge(formats: :html, status: :unprocessable_entity)
11
11
  else
12
12
  redirect_to navigation_location
13
13
  end
@@ -40,7 +40,9 @@ module Account::FormsHelper
40
40
  def options_for(form, method)
41
41
  # e.g. "scaffolding/completely_concrete/tangible_things.fields.text_area_value.options"
42
42
  path = [model_key(form), (current_fields_namespace || :fields), method, :options]
43
- t(path.compact.join("."))
43
+ options = t(path.compact.join("."))
44
+ return options unless options.is_a?(Hash)
45
+ options.stringify_keys
44
46
  end
45
47
 
46
48
  def legacy_label_for(form, method)
@@ -1,10 +1,10 @@
1
1
  module AttributesHelper
2
2
  def current_attributes_object
3
- @_attributes_helper_objects ? @_attributes_helper_objects.last : nil
3
+ @_attributes_helper_objects&.last
4
4
  end
5
5
 
6
6
  def current_attributes_strategy
7
- @_attributes_helper_strategies ? @_attributes_helper_strategies.last : nil
7
+ @_attributes_helper_strategies&.last
8
8
  end
9
9
 
10
10
  def with_attribute_settings(options)
@@ -9,7 +9,7 @@ export default class extends Controller {
9
9
  document.execCommand('copy')
10
10
  this.buttonTarget.innerHTML = '<i id="copied" class="fas fa-check w-4 h-4 block text-green-600"></i>'
11
11
  setTimeout(function () {
12
- document.getElementById('copied').innerHTML = '<i class="far fa-copy w-4 h-4 block text-gray-600"></i>'
12
+ document.getElementById('copied').innerHTML = '<i class="far fa-copy w-4 h-4 block text-slate-600"></i>'
13
13
  }, 1500)
14
14
  }
15
15
  }
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ disableTeamButton(event) {
5
+ document.body.style.pointerEvents = "none";
6
+ }
7
+ }
@@ -0,0 +1,26 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = [ "menuItemHeader", "menuItemGroup", "menuItemLink" ];
5
+
6
+ showSubmenu() {
7
+ this.menuItemGroupTarget.classList.remove('invisible');
8
+ }
9
+
10
+ // TODO: Stimulus JS should be able to use `keydown.tab` and `keydown.tab+shift` as actions.
11
+ // https://stimulus.hotwired.dev/reference/actions#keyboardevent-filter
12
+ hideSubmenu(event) {
13
+ let hideMenu = false;
14
+
15
+ // If we're tabbing forward and go outside of the submenu, we hide the group.
16
+ // Else if we're tabbing backwards and go outside via the menu item header, we hide the group.
17
+ if(event.key == 'Tab' && !event.shiftKey) {
18
+ let lastIndex = this.menuItemLinkTargets.length - 1;
19
+ hideMenu = event.target == this.menuItemLinkTargets[lastIndex]
20
+ } else if (event.key == 'Tab' && event.shiftKey) {
21
+ hideMenu = event.target == this.menuItemHeaderTarget
22
+ }
23
+
24
+ if(hideMenu) { this.menuItemGroupTarget.classList.add('invisible'); }
25
+ }
26
+ }
@@ -3,19 +3,23 @@ import { identifierForContextKey } from "@hotwired/stimulus-webpack-helpers"
3
3
  import BulkActionFormController from './bulk_action_form_controller'
4
4
  import BulkActionsController from './bulk_actions_controller'
5
5
  import ClipboardController from './clipboard_controller'
6
+ import DesktopMenuController from './desktop_menu_controller'
6
7
  import FormController from './form_controller'
7
8
  import MobileMenuController from './mobile_menu_controller'
8
9
  import TextToggleController from './text_toggle_controller'
9
10
  import SelectAllController from './select_all_controller'
11
+ import ConnectionWorkflowController from './connection_workflow_controller'
10
12
 
11
13
  export const controllerDefinitions = [
12
14
  [BulkActionFormController, 'bulk_action_form_controller.js'],
13
15
  [BulkActionsController, 'bulk_actions_controller.js'],
14
16
  [ClipboardController, 'clipboard_controller.js'],
17
+ [DesktopMenuController, 'desktop_menu_controller.js'],
15
18
  [FormController, 'form_controller.js'],
16
19
  [MobileMenuController, 'mobile_menu_controller.js'],
17
20
  [TextToggleController, 'text_toggle_controller.js'],
18
21
  [SelectAllController, 'select_all_controller.js'],
22
+ [ConnectionWorkflowController, 'connection_workflow_controller.js'],
19
23
  ].map(function(d) {
20
24
  const key = d[1]
21
25
  const controller = d[0]
@@ -1,2 +1,3 @@
1
1
  export * from './controllers'
2
- import './electron'
2
+ import './electron'
3
+ import './support/turn'
@@ -0,0 +1,183 @@
1
+ // MIT License
2
+ //
3
+ // Copyright (c) 2021 Dom Christie
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in all
13
+ // copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ // SOFTWARE.
22
+
23
+ class Turn {
24
+ constructor (action) {
25
+ this.action = action
26
+ this.beforeExitClasses = new Set()
27
+ this.exitClasses = new Set()
28
+ this.enterClasses = new Set()
29
+ }
30
+
31
+ exit () {
32
+ this.animateOut = animationsEnd('[data-turn-exit]')
33
+ this.addClasses('before-exit')
34
+ requestAnimationFrame(() => {
35
+ this.addClasses('exit')
36
+ this.removeClasses('before-exit')
37
+ })
38
+ }
39
+
40
+ async beforeEnter (event) {
41
+ if (this.action === 'restore') return
42
+
43
+ event.preventDefault()
44
+
45
+ if (this.isPreview) {
46
+ this.hasPreview = true
47
+ await this.animateOut
48
+ } else {
49
+ await this.animateOut
50
+ if (this.animateIn) await this.animateIn
51
+ }
52
+
53
+ event.detail.resume()
54
+ }
55
+
56
+ async enter () {
57
+ this.removeClasses('exit')
58
+
59
+ if (this.shouldAnimateEnter) {
60
+ this.animateIn = animationsEnd('[data-turn-enter]')
61
+ this.addClasses('enter')
62
+ }
63
+ }
64
+
65
+ async complete () {
66
+ await this.animateIn
67
+ this.removeClasses('enter')
68
+ }
69
+
70
+ abort () {
71
+ this.removeClasses('before-exit')
72
+ this.removeClasses('exit')
73
+ this.removeClasses('enter')
74
+ }
75
+
76
+ get shouldAnimateEnter () {
77
+ if (this.action === 'restore') return false
78
+ if (this.isPreview) return true
79
+ if (this.hasPreview) return false
80
+ return true
81
+ }
82
+
83
+ get isPreview () {
84
+ return document.documentElement.hasAttribute('data-turbo-preview')
85
+ }
86
+
87
+ addClasses (type) {
88
+ document.documentElement.classList.add(`turn-${type}`)
89
+
90
+ Array.from(document.querySelectorAll(`[data-turn-${type}]`)).forEach((element) => {
91
+ element.dataset[`turn${pascalCase(type)}`].split(/\s+/).forEach((klass) => {
92
+ if (klass) {
93
+ element.classList.add(klass)
94
+ this[`${camelCase(type)}Classes`].add(klass)
95
+ }
96
+ })
97
+ })
98
+ }
99
+
100
+ removeClasses (type) {
101
+ document.documentElement.classList.remove(`turn-${type}`)
102
+
103
+ Array.from(document.querySelectorAll(`[data-turn-${type}]`)).forEach((element) => {
104
+ this[`${camelCase(type)}Classes`].forEach((klass) => element.classList.remove(klass))
105
+ })
106
+ }
107
+ }
108
+
109
+ Turn.start = function () {
110
+ if (motionSafe()) {
111
+ for (var event in this.eventListeners) {
112
+ addEventListener(event, this.eventListeners[event])
113
+ }
114
+ }
115
+ }
116
+
117
+ Turn.stop = function () {
118
+ for (var event in this.eventListeners) {
119
+ removeEventListener(event, this.eventListeners[event])
120
+ }
121
+ delete this.currentTurn
122
+ }
123
+
124
+ Turn.eventListeners = {
125
+ 'turbo:visit': function (event) {
126
+ if (this.currentTurn) this.currentTurn.abort()
127
+ this.currentTurn = new this(event.detail.action)
128
+ this.currentTurn.exit()
129
+ }.bind(Turn),
130
+ 'turbo:before-render': function (event) {
131
+ this.currentTurn.beforeEnter(event)
132
+ }.bind(Turn),
133
+ 'turbo:render': function () {
134
+ this.currentTurn.enter()
135
+ }.bind(Turn),
136
+ 'turbo:load': function () {
137
+ if (this.currentTurn) this.currentTurn.complete()
138
+ }.bind(Turn),
139
+ 'popstate': function () {
140
+ if (this.currentTurn && this.currentTurn.action !== 'restore') {
141
+ this.currentTurn.abort()
142
+ }
143
+ }.bind(Turn)
144
+ }
145
+
146
+ function prefersReducedMotion () {
147
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
148
+ return !mediaQuery || mediaQuery.matches
149
+ }
150
+
151
+ function motionSafe () {
152
+ return !prefersReducedMotion()
153
+ }
154
+
155
+ function animationsEnd (selector) {
156
+ const elements = [...document.querySelectorAll(selector)]
157
+
158
+ return Promise.all(elements.map((element) => {
159
+ return new Promise((resolve) => {
160
+ function listener () {
161
+ element.removeEventListener('animationend', listener)
162
+ resolve()
163
+ }
164
+ element.addEventListener('animationend', listener)
165
+ })
166
+ }))
167
+ }
168
+
169
+ function pascalCase (string) {
170
+ return string.split(/[^\w]/).map(capitalize).join('')
171
+ }
172
+
173
+ function camelCase (string) {
174
+ return string.split(/[^\w]/).map(
175
+ (w, i) => i === 0 ? w.toLowerCase() : capitalize(w)
176
+ ).join('')
177
+ }
178
+
179
+ function capitalize (string) {
180
+ return string.replace(/^\w/, (c) => c.toUpperCase())
181
+ }
182
+
183
+ Turn.start()
@@ -6,7 +6,11 @@ class Billing::MockLimiter
6
6
  []
7
7
  end
8
8
 
9
- def can?(action, model)
9
+ def can?(action, model, count: 1)
10
10
  true
11
11
  end
12
+
13
+ def exhausted?(model)
14
+ false
15
+ end
12
16
  end
@@ -82,18 +82,17 @@ module Records::Base
82
82
 
83
83
  # TODO This should really be in the API package and included from there.
84
84
  if defined?(BulletTrain::Api)
85
- def to_api_json
86
- # TODO So many performance improvements available here.
87
- controller = "Api::#{BulletTrain::Api.current_version.upcase}::ApplicationController".constantize.new
85
+ # We default this to the current version of the API, but developers can request a specific version.
86
+ def to_api_json(api_version = BulletTrain::Api.current_version_numeric)
87
+ controller = "Api::V#{api_version}::ApplicationController".constantize.new
88
88
  # TODO We need to fix host names here.
89
89
  controller.request = ActionDispatch::Request.new({})
90
90
  local_class_key = self.class.name.underscore.split("/").last.to_sym
91
- controller.render_to_string(
92
- "api/#{BulletTrain::Api.current_version}/#{self.class.name.underscore.pluralize}/_#{local_class_key}",
93
- locals: {
94
- local_class_key => self
95
- }
96
- )
91
+
92
+ # Returns a hash, not string.
93
+ JbuilderTemplate.new(controller.view_context) do |json|
94
+ json.partial! "api/#{BulletTrain::Api.current_version}/#{self.class.name.underscore.pluralize}/#{local_class_key}", local_class_key => self
95
+ end.attributes!
97
96
  end
98
97
  end
99
98
  end
@@ -3,13 +3,13 @@ module Users::Base
3
3
 
4
4
  included do
5
5
  if two_factor_authentication_enabled?
6
- devise :two_factor_authenticatable, :two_factor_backupable, otp_secret_encryption_key: ENV["TWO_FACTOR_ENCRYPTION_KEY"]
6
+ devise :two_factor_authenticatable, :two_factor_backupable
7
7
  else
8
8
  devise :database_authenticatable
9
9
  end
10
10
 
11
11
  devise :omniauthable
12
- devise :pwned_password
12
+ devise :pwned_password if BulletTrain::Configuration.default.strong_passwords
13
13
  devise :registerable
14
14
  devise :recoverable
15
15
  devise :rememberable
@@ -23,10 +23,10 @@
23
23
  <% Membership.assignable_roles.each do |role| %>
24
24
  <% if current_membership.can_manage_role?(role) %>
25
25
  <div class="flex items-top">
26
- <%= fields.check_box :role_ids, {multiple: true, class: "h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded mt-0.5"}, role.id, nil %>
26
+ <%= fields.check_box :role_ids, {multiple: true, class: "h-4 w-4 text-blue focus:ring-blue-dark border-slate-300 rounded mt-0.5"}, role.id, nil %>
27
27
  <label for="invitation_membership_attributes_role_ids_<%= role.id %>" class="ml-2 block select-none">
28
28
  <span><%= t('invitations.form.invite_as', role_key: t("memberships.fields.role_ids.options.#{role.key}.label")) %></span>
29
- <div class="mt-0.5 text-gray-400 font-light leading-normal">
29
+ <div class="mt-0.5 text-slate-400 font-light leading-normal">
30
30
  <%= t("memberships.fields.role_ids.options.#{role.key}.description") %>
31
31
  </div>
32
32
  </label>
@@ -24,10 +24,10 @@
24
24
  <% Membership.assignable_roles.each do |role| %>
25
25
  <% if role.manageable_by?(current_membership.roles) %>
26
26
  <div class="flex items-top">
27
- <%= form.check_box :role_ids, {multiple: true, class: "h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded mt-0.5"}, role.id, nil %>
27
+ <%= form.check_box :role_ids, {multiple: true, class: "h-4 w-4 text-blue focus:ring-blue-800 border-slate-300 rounded mt-0.5"}, role.id, nil %>
28
28
  <label for="membership_role_ids_<%= role.id %>" class="ml-2 block select-none">
29
29
  <%= t('.grant_privileges_of', role_key: t(".fields.role_ids.options.#{role.key}.label")) %>
30
- <div class="mt-0.5 text-gray-400 font-light leading-normal">
30
+ <div class="mt-0.5 text-slate-400 font-light leading-normal">
31
31
  <%= t(".fields.role_ids.options.#{role.key}.description") %>
32
32
  </div>
33
33
  </label>
@@ -8,7 +8,7 @@
8
8
  <div class="ml-3">
9
9
  <span class="group-hover:underline"><%= membership.label_string %></span>
10
10
  <% if membership.unclaimed? %>
11
- <span class="ml-1.5 px-2 inline-flex text-xs text-green-dark bg-green-light border border-green-dark rounded-md">
11
+ <span class="ml-1.5 px-2 inline-flex text-xs text-green-800 bg-green-light border border-green-800 rounded-md">
12
12
  Invited
13
13
  </span>
14
14
  <% end %>
@@ -1,11 +1,18 @@
1
1
  <% team ||= @team %>
2
- <% unless current_user.one_team? %>
2
+
3
+ <% if (action_name == 'show' && controller_name == 'teams') || current_user.one_team? %>
4
+ <%= render 'account/shared/breadcrumb', label: t('.dashboard'), url: [:account, team], first: true %>
5
+ <% end %>
6
+
7
+ <% unless current_user.one_team? %>
3
8
  <%= render 'account/shared/breadcrumb', label: t('.label'), url: [:account, :teams], first: true %>
4
9
  <% if team&.persisted? %>
5
10
  <%= render 'account/shared/breadcrumb', label: team.name, url: [:account, team] %>
6
11
  <% end %>
7
12
  <% end %>
8
- <%= render 'account/shared/breadcrumbs/actions', only_for: 'teams' %>
9
- <% if (action_name == 'show' && controller_name == 'teams') || current_user.one_team? %>
10
- <%= render 'account/shared/breadcrumb', label: 'Dashboard', url: [:account, team] %>
11
- <% end %>
13
+
14
+ <%if action_name == 'edit' %>
15
+ <%= render 'account/shared/breadcrumb', label: t('.team_settings') %>
16
+ <%else %>
17
+ <%= render 'account/shared/breadcrumbs/actions', only_for: 'teams' %>
18
+ <% end %>
@@ -1,9 +1,9 @@
1
- <li class="bg-white shadow overflow-hidden sm:rounded-md dark:bg-sealBlue-400">
2
- <%= link_to [:account, team], class: "group block hover:bg-gray-50 dark:hover:bg-sealBlue-400 dark:text-sealBlue-800" do %>
1
+ <li class="bg-white shadow overflow-hidden sm:rounded-md dark:bg-slate-400">
2
+ <%= link_to [:account, team], class: "group block hover:bg-slate-50 dark:hover:bg-slate-400 dark:text-slate-800" do %>
3
3
  <div class="px-4 py-4 flex items-center sm:pl-8 sm:pr-6">
4
4
  <div class="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
5
5
  <div>
6
- <div class="flex text-xl font-semibold text-blue uppercase group-hover:text-blue-dark tracking-widest dark:text-white">
6
+ <div class="flex text-xl font-semibold text-blue uppercase group-hover:text-blue-800 tracking-widest dark:text-white">
7
7
  <%= team.name %>
8
8
  </div>
9
9
  </div>
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
  </div>
16
16
  <div class="ml-5 flex-shrink-0">
17
- <svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
17
+ <svg class="h-5 w-5 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
18
18
  <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
19
19
  </svg>
20
20
  </div>
@@ -0,0 +1 @@
1
+ $("#two-factor").html("<%= j render partial: "devise/registrations/two_factor", locals: {verified: @verified}%>");
@@ -3,7 +3,7 @@
3
3
  <% p.content_for :title, @title %>
4
4
  <% p.content_for :body do %>
5
5
  <% within_fields_namespace(:update_self) do %>
6
- <%= form_for(resource, as: resource_name, url: password_path(resource_name), class: 'form', html: { method: :put }) do |f| %>
6
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: 'form', method: :put }) do |f| %>
7
7
  <%= f.hidden_field :reset_password_token %>
8
8
  <%= render 'account/shared/forms/errors', form: f, attributes: [:reset_password_token] %>
9
9
  <%= render 'shared/fields/password_field', form: f, method: :password, options: {autofocus: true, autocomplete: "off"} %>
@@ -1,9 +1,9 @@
1
1
  <%= render 'account/shared/box', divider: @backup_codes do |p| %>
2
2
  <% p.content_for :title, t("users.edit.two_factor.header") %>
3
3
  <% p.content_for :description, t("users.edit.two_factor.description_#{@user.otp_required_for_login? ? 'enabled' : 'disabled'}") %>
4
- <% p.content_for :body do %>
5
- <% if current_user.otp_required_for_login? %>
6
- <% if @backup_codes %>
4
+ <% if current_user.otp_secret %>
5
+ <% if @backup_codes %>
6
+ <% p.content_for :body do %>
7
7
 
8
8
  <%= render 'account/shared/alert' do %>
9
9
  <%= t('users.edit.two_factor.warning').html_safe %>
@@ -29,14 +29,35 @@
29
29
  <% end %>
30
30
  </center>
31
31
 
32
+ <%= form_for current_user, url: verify_account_two_factor_path, method: :post, remote:true, html: {class: 'form'} do |form| %>
33
+ <div class="py-4">
34
+ <%= render 'shared/fields/text_field', form: form, method: :otp_attempt %>
35
+ </div>
36
+ <%= form.submit t('users.edit.two_factor.buttons.verify'), class: 'button' %>
37
+ <% end %>
32
38
  <% end %>
33
39
  <% end %>
34
40
  <% end %>
35
41
  <% p.content_for :actions do %>
36
- <% if current_user.otp_required_for_login? %>
37
- <%= link_to t('users.edit.two_factor.buttons.disable'), account_two_factor_path, method: :delete, remote: true, class: "button" %>
38
- <% else %>
39
- <%= link_to t('users.edit.two_factor.buttons.enable'), account_two_factor_path, method: :post, remote: true, class: "button" %>
40
- <% end %>
42
+ <div class="<%= 'hidden' if @backup_codes %> space-y">
43
+ <% if local_assigns.has_key? :verified %>
44
+ <% if verified %>
45
+ <%= render 'account/shared/alert', color: 'blue' do %>
46
+ <%= t('users.edit.two_factor.verification_success').html_safe %>
47
+ <% end %>
48
+ <% else %>
49
+ <%= render 'account/shared/alert' do %>
50
+ <%= t('users.edit.two_factor.verification_fail').html_safe %>
51
+ <% end %>
52
+ <% end %>
53
+ <% end %>
54
+
55
+ <% if current_user.otp_required_for_login? %>
56
+ <%= link_to t('users.edit.two_factor.buttons.disable'), account_two_factor_path, method: :delete, remote: true, class: "button" %>
57
+ <% else %>
58
+ <%= link_to t('users.edit.two_factor.buttons.enable'), account_two_factor_path, method: :post, remote: true, class: "button" %>
59
+ <% end %>
60
+ </div>
41
61
  <% end %>
42
62
  <% end %>
63
+
@@ -2,7 +2,8 @@
2
2
  <% p.content_for :title, t('devise.headers.sign_in') %>
3
3
  <% p.content_for :body do %>
4
4
  <% within_fields_namespace(:self) do %>
5
- <%= form_for resource, as: resource_name, url: two_factor_authentication_enabled? ? users_pre_otp_path : session_path(resource_name), remote: two_factor_authentication_enabled?, html: {class: 'form'}, authenticity_token: true, data: {turbo: !user_return_to_is_oauth} do |form| %>
5
+ <%# TODO: Turbo is set to `false` for now, but we may want to only bypass Turbo for JavaScript-based requests in the future. %>
6
+ <%= form_for resource, as: resource_name, url: two_factor_authentication_enabled? ? users_pre_otp_path : session_path(resource_name), remote: two_factor_authentication_enabled?, html: {class: 'form'}, authenticity_token: true, data: {turbo: false} do |form| %>
6
7
  <% with_field_settings form: form do %>
7
8
  <%= render 'account/shared/notices', form: form %>
8
9
  <%= render 'account/shared/forms/errors', form: form %>
@@ -43,8 +44,9 @@
43
44
  </div>
44
45
 
45
46
  <% if devise_mapping.rememberable? %>
47
+ <% # TODO This needs to be its own component. Can't have this kind of styling here. %>
46
48
  <div class="flex items-center">
47
- <%= form.check_box :remember_me, class: "h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded" %>
49
+ <%= form.check_box :remember_me, class: "h-4 w-4 text-blue focus:ring-blue-800 border-slate-300 rounded dark:bg-slate-800 dark:border-slate-900" %>
48
50
  <%= form.label :remember_me, class: "ml-2 block" %>
49
51
  </div>
50
52
  <% end %>
@@ -32,7 +32,7 @@
32
32
  <meta property="og:url" content="<%= request.base_url + request.path %>" />
33
33
  <meta property="og:description" content="<%= description.truncate(200) %>" />
34
34
  </head>
35
- <body class="bg-light-gradient text-gray-700 text-sm font-normal dark:bg-dark-gradient dark:text-darkPrimary-300">
35
+ <body class="bg-light-gradient text-slate-700 text-sm font-normal dark:bg-800-gradient dark:text-slate-300">
36
36
  <div class="md:p-5">
37
37
  <div class="h-screen md:h-auto overflow-hidden md:rounded-lg flex shadow"
38
38
  data-controller="mobile-menu"
@@ -40,7 +40,7 @@
40
40
  >
41
41
 
42
42
  <% menu = capture do %>
43
- <div class="flex items-center flex-shrink-0 p-4 bg-blue-darker md:rounded-tl-lg">
43
+ <div class="flex items-center flex-shrink-0 p-4 bg-blue-700 md:rounded-tl-lg">
44
44
  <%= image_tag image_path("logo/logo.png"), class: 'h-5 w-auto mx-auto' %>
45
45
 
46
46
  <div class="lg:hidden absolute right-0">
@@ -147,7 +147,7 @@
147
147
  <% end %>
148
148
  <% end %>
149
149
 
150
- <%= render 'account/shared/menu/item', url: 'docs/application-options.md', label: 'Application Options' do |p| %>
150
+ <%= render 'account/shared/menu/item', url: '/docs/application-options', label: 'Application Options' do |p| %>
151
151
  <% p.content_for :icon do %>
152
152
  <i class="fal fa-gear ti ti-settings"></i>
153
153
  <% end %>
@@ -308,7 +308,7 @@
308
308
  data-transition-leave-start="translate-x-0"
309
309
  data-transition-leave-end="-translate-x-full"
310
310
 
311
- class="relative flex-1 flex flex-col max-w-xs w-full pb-4 bg-dark-gradient shadow-xl"
311
+ class="relative flex-1 flex flex-col max-w-xs w-full pb-4 bg-800-gradient shadow-xl"
312
312
  >
313
313
  <%= menu %>
314
314
  </div>
@@ -316,16 +316,16 @@
316
316
  </div>
317
317
  </div>
318
318
 
319
- <div class="hidden lg:flex lg:flex-shrink-0 overflow-y-auto bg-gradient-to-b from-primary-700 to-primary-800 dark:from-darkPrimary-800 dark:to-darkPrimary-800">
319
+ <div class="hidden lg:flex lg:flex-shrink-0 overflow-y-auto bg-gradient-to-b from-primary-700 to-primary-800 dark:from-slate-800 dark:to-slate-800">
320
320
  <div class="w-64">
321
321
  <%= menu %>
322
322
  </div>
323
323
  </div>
324
324
 
325
- <div class="flex flex-col w-0 flex-1 overflow-y-auto bg-gray-100 dark:bg-darkPrimary-800 lg:border-l dark:border-gray-500">
325
+ <div class="flex flex-col w-0 flex-1 overflow-y-auto bg-slate-100 dark:bg-slate-800 lg:border-l dark:border-slate-500">
326
326
  <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0">
327
327
 
328
- <button class="lg:hidden h-12 w-12 ml-1 flex-none inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue"
328
+ <button class="lg:hidden h-12 w-12 ml-1 flex-none inline-flex items-center justify-center rounded-md text-slate-500 hover:text-slate-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue"
329
329
  data-action="mobile-menu#open"
330
330
  >
331
331
  <span class="sr-only">Open Application Menu</span>