kaze 0.5.0 → 0.6.0

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kaze/commands/install_command.rb +8 -0
  3. data/lib/kaze/commands/installs_hotwire_stack.rb +5 -2
  4. data/lib/kaze/commands/installs_inertia_stacks.rb +13 -3
  5. data/lib/kaze/version.rb +1 -1
  6. data/stubs/default/app/forms/auth/login_form.rb +2 -8
  7. data/stubs/default/app/forms/update_profile_information_form.rb +1 -1
  8. data/stubs/default/app/models/auth.rb +57 -0
  9. data/stubs/default/app/models/current.rb +1 -1
  10. data/stubs/default/app/validators/current_password_validator.rb +1 -1
  11. data/stubs/default/app/views/layouts/mailer.html.erb +367 -372
  12. data/stubs/default/app/views/layouts/mailer.text.erb +1 -4
  13. data/stubs/default/app/views/user_mailer/reset_password.html.erb +21 -26
  14. data/stubs/default/test/factories/users.rb +7 -0
  15. data/stubs/default/test/integration/auth/authentication_test.rb +43 -0
  16. data/stubs/default/test/integration/auth/password_reset_test.rb +41 -0
  17. data/stubs/default/test/integration/auth/registration_test.rb +21 -0
  18. data/stubs/default/test/integration/password_update_test.rb +28 -0
  19. data/stubs/default/test/integration/profile_test.rb +51 -0
  20. data/stubs/default/test/test_helper.rb +38 -0
  21. data/stubs/hotwire/app/components/dropdown_component.html.erb +17 -18
  22. data/stubs/hotwire/app/components/modal_component.html.erb +55 -59
  23. data/stubs/hotwire/app/controllers/application_controller.rb +1 -0
  24. data/stubs/hotwire/app/controllers/auth/authenticated_session_controller.rb +10 -7
  25. data/stubs/hotwire/app/controllers/auth/new_password_controller.rb +3 -1
  26. data/stubs/hotwire/app/controllers/auth/password_reset_link_controller.rb +3 -1
  27. data/stubs/hotwire/app/controllers/auth/registered_user_controller.rb +4 -2
  28. data/stubs/hotwire/app/controllers/concerns/authenticate.rb +5 -20
  29. data/stubs/hotwire/app/controllers/concerns/redirect_if_authenticated.rb +19 -0
  30. data/stubs/hotwire/app/controllers/concerns/set_current_auth.rb +9 -0
  31. data/stubs/hotwire/app/controllers/password_controller.rb +1 -1
  32. data/stubs/hotwire/app/controllers/profile_controller.rb +6 -4
  33. data/stubs/hotwire/app/controllers/welcome_controller.rb +1 -1
  34. data/stubs/hotwire/app/javascript/application.js +3 -3
  35. data/stubs/hotwire/app/views/auth/forgot_password.html.erb +12 -17
  36. data/stubs/hotwire/app/views/auth/login.html.erb +0 -9
  37. data/stubs/hotwire/app/views/auth/register.html.erb +0 -13
  38. data/stubs/hotwire/app/views/auth/reset_password.html.erb +0 -7
  39. data/stubs/hotwire/app/views/dashboard/index.html.erb +9 -10
  40. data/stubs/hotwire/app/views/layouts/_navigation.html.erb +77 -87
  41. data/stubs/hotwire/app/views/layouts/application.html.erb +0 -9
  42. data/stubs/hotwire/app/views/layouts/guest.html.erb +0 -6
  43. data/stubs/hotwire/app/views/profile/edit.html.erb +19 -22
  44. data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +32 -42
  45. data/stubs/hotwire/app/views/profile/partials/_update_password_form.html.erb +42 -55
  46. data/stubs/hotwire/app/views/profile/partials/_update_profile_information_form.html.erb +36 -46
  47. data/stubs/hotwire/app/views/welcome/index.html.erb +34 -46
  48. data/stubs/hotwire/config/tailwind.config.js +2 -2
  49. data/stubs/inertia-common/app/controllers/application_controller.rb +1 -0
  50. data/stubs/inertia-common/app/controllers/auth/authenticated_session_controller.rb +10 -7
  51. data/stubs/inertia-common/app/controllers/auth/new_password_controller.rb +3 -1
  52. data/stubs/inertia-common/app/controllers/auth/password_reset_link_controller.rb +3 -1
  53. data/stubs/inertia-common/app/controllers/auth/registered_user_controller.rb +4 -2
  54. data/stubs/inertia-common/app/controllers/concerns/authenticate.rb +5 -20
  55. data/stubs/inertia-common/app/controllers/concerns/handle_inertia_requests.rb +1 -1
  56. data/stubs/inertia-common/app/controllers/concerns/redirect_if_authenticated.rb +19 -0
  57. data/stubs/inertia-common/app/controllers/concerns/set_current_auth.rb +9 -0
  58. data/stubs/inertia-common/app/controllers/password_controller.rb +1 -1
  59. data/stubs/inertia-common/app/controllers/profile_controller.rb +5 -3
  60. data/stubs/inertia-common/app/controllers/welcome_controller.rb +1 -1
  61. data/stubs/inertia-common/test/integration/password_update_test.rb +28 -0
  62. data/stubs/inertia-common/test/integration/profile_test.rb +51 -0
  63. data/stubs/inertia-react-ts/app/javascript/Components/ApplicationLogo.tsx +13 -9
  64. data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +15 -12
  65. data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +20 -15
  66. data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +119 -87
  67. data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +14 -7
  68. data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +18 -7
  69. data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +60 -60
  70. data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +21 -16
  71. data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +20 -15
  72. data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +19 -14
  73. data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +22 -16
  74. data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +35 -24
  75. data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +157 -117
  76. data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +15 -15
  77. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +52 -49
  78. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +90 -82
  79. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +118 -115
  80. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +63 -60
  81. data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +23 -17
  82. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +31 -27
  83. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +109 -99
  84. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +121 -113
  85. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +76 -69
  86. data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +87 -63
  87. data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +32 -25
  88. data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +0 -4
  89. data/stubs/inertia-react-ts/config/tailwind.config.js +2 -2
  90. data/stubs/inertia-react-ts/vite.config.ts +2 -5
  91. data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +10 -6
  92. data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +18 -18
  93. data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +5 -5
  94. data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +60 -57
  95. data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +9 -9
  96. data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +7 -7
  97. data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +6 -6
  98. data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +84 -74
  99. data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +12 -12
  100. data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +5 -5
  101. data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +12 -12
  102. data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +13 -13
  103. data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +13 -13
  104. data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +168 -136
  105. data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +15 -13
  106. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +56 -49
  107. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +78 -72
  108. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +101 -97
  109. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +71 -68
  110. data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +22 -14
  111. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +34 -30
  112. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +87 -83
  113. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +105 -98
  114. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +69 -59
  115. data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +74 -47
  116. data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +0 -4
  117. data/stubs/inertia-vue-ts/config/tailwind.config.js +2 -2
  118. data/stubs/inertia-vue-ts/vite.config.ts +2 -5
  119. metadata +18 -6
  120. data/stubs/hotwire/bin/vite +0 -27
  121. data/stubs/inertia-common/Procfile.dev +0 -3
  122. /data/stubs/{hotwire → default}/Procfile.dev +0 -0
  123. /data/stubs/hotwire/app/javascript/{alpinejs.js → alpinejs.stub} +0 -0
@@ -1,13 +1,10 @@
1
1
  <!-- Header -->
2
2
  <%= ENV.fetch("APP_NAME", "Rails") %>: <%= root_url %>
3
-
4
3
  <!-- Body -->
5
4
  <%= yield %>
6
-
7
5
  <!-- Subcopy -->
8
6
  <% if content_for?(:subcopy) %>
9
- <%= yield :subcopy %>
7
+ <%= yield :subcopy %>
10
8
  <% end %>
11
-
12
9
  <!-- Footer -->
13
10
  © <%= Time.new.year %> <%= ENV.fetch("APP_NAME", "Rails") %>. All rights reserved.
@@ -1,39 +1,34 @@
1
1
  <!-- Greeting -->
2
2
  <h1>Hello!</h1>
3
-
4
3
  <!-- Intro Lines -->
5
4
  <p>You are receiving this email because we received a password reset request for your account.</p>
6
-
7
5
  <!-- Action Button -->
8
6
  <table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
9
- <tr>
10
- <td align="center">
11
- <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
12
- <tr>
13
- <td align="center">
14
- <table border="0" cellpadding="0" cellspacing="0" role="presentation">
15
- <tr>
16
- <td>
17
- <a href="<%= password_reset_url(token: params[:token]) %>" class="button button-primary" target="_blank" rel="noopener">Reset Password</a>
18
- </td>
19
- </tr>
7
+ <tr>
8
+ <td align="center">
9
+ <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
10
+ <tr>
11
+ <td align="center">
12
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation">
13
+ <tr>
14
+ <td>
15
+ <a href="<%= password_reset_url(token: params[:token]) %>" class="button button-primary" target="_blank" rel="noopener">Reset Password</a>
16
+ </td>
17
+ </tr>
18
+ </table>
19
+ </td>
20
+ </tr>
21
+ </table>
22
+ </td>
23
+ </tr>
20
24
  </table>
21
- </td>
22
- </tr>
23
- </table>
24
- </td>
25
- </tr>
26
- </table>
27
-
28
25
  <!-- Outro Lines -->
29
26
  <p>This password reset link will expire in 60 minutes.</p>
30
-
31
27
  <p>If you did not request a password reset, no further action is required.</p>
32
-
33
28
  <!-- Salutation -->
34
- <p>Regards,<br><%= ENV.fetch("APP_NAME", "Rails") %></p>
35
-
29
+ <p>Regards,<br>
30
+ <%= ENV.fetch("APP_NAME", "Rails") %></p>
36
31
  <!-- Subcopy -->
37
32
  <% content_for :subcopy do %>
38
- <p>If you're having trouble clicking the "Reset Password" button, copy and paste the URL below into your web browser: <span class="break-all"><%= link_to password_reset_url(token: params[:token]), password_reset_url(token: params[:token]) %><span></p>
39
- <% end %>
33
+ <p>If you're having trouble clicking the "Reset Password" button, copy and paste the URL below into your web browser: <span class="break-all"><%= link_to password_reset_url(token: params[:token]), password_reset_url(token: params[:token]) %><span></p>
34
+ <% end %>
@@ -0,0 +1,7 @@
1
+ FactoryBot.define do
2
+ factory :user do
3
+ name { Faker::Name.name }
4
+ email { Faker::Internet.email }
5
+ password { 'password' }
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ class Auth::AuthenticationTest < ActionDispatch::IntegrationTest
4
+ test 'login screen can be rendered' do
5
+ get login_path
6
+
7
+ assert_response :success
8
+ end
9
+
10
+ test 'users can authenticate using the login screen' do
11
+ user = FactoryBot.create :user
12
+
13
+ post login_path, params: {
14
+ email: user.email,
15
+ password: 'password'
16
+ }
17
+
18
+ assert_authenticated
19
+ assert_redirected_to dashboard_path
20
+ end
21
+
22
+ test 'users cannot authenticate with invalid password' do
23
+ user = FactoryBot.create :user
24
+
25
+ post login_path, params: {
26
+ email: user.email,
27
+ password: 'wrong-password'
28
+ }
29
+
30
+ assert_guest
31
+ end
32
+
33
+ test 'users can logout' do
34
+ user = FactoryBot.create :user
35
+
36
+ acting_as user
37
+
38
+ post logout_path
39
+
40
+ assert_guest
41
+ assert_redirected_to '/'
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+
3
+ class Auth::PasswordResetTest < ActionDispatch::IntegrationTest
4
+ test 'reset password link screen can be rendered' do
5
+ get password_request_path
6
+
7
+ assert_response :success
8
+ end
9
+
10
+ test 'reset password link can be requested' do
11
+ user = FactoryBot.create :user
12
+ token = user.generate_token_for(:password_reset)
13
+ email = UserMailer.with(user: user, token: token).reset_password
14
+
15
+ post password_email_path, params: { email: user.email }
16
+
17
+ assert_enqueued_email_with UserMailer, :reset_password, params: { user: user, token: token } do
18
+ email.deliver_later
19
+ end
20
+ end
21
+
22
+ test 'reset password screen can be rendered' do
23
+ user = FactoryBot.create :user
24
+
25
+ get password_reset_path(token: user.generate_token_for(:password_reset))
26
+
27
+ assert_response :success
28
+ end
29
+
30
+ test 'password can be reset_with_valid_token' do
31
+ user = FactoryBot.create :user
32
+
33
+ post password_store_path, params: {
34
+ token: user.generate_token_for(:password_reset),
35
+ password: 'password',
36
+ password_confirmation: 'password'
37
+ }
38
+
39
+ assert_redirected_to login_path
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ class Auth::RegistrationTest < ActionDispatch::IntegrationTest
4
+ test 'registration screen can be rendered' do
5
+ get register_path
6
+
7
+ assert_response :success
8
+ end
9
+
10
+ test 'new users can register' do
11
+ post register_path, params: {
12
+ name: 'Test User',
13
+ email: 'test@example.com',
14
+ password: 'password',
15
+ password_confirmation: 'password'
16
+ }
17
+
18
+ assert_authenticated
19
+ assert_redirected_to dashboard_url
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class PasswordUpdateTest < ActionDispatch::IntegrationTest
4
+ test 'password can be updated' do
5
+ user = FactoryBot.create :user
6
+
7
+ acting_as(user).put password_update_path, params: {
8
+ current_password: 'password',
9
+ password: 'new-password',
10
+ password_confirmation: 'new-password'
11
+ }
12
+
13
+ assert_redirected_to profile_edit_path
14
+ assert BCrypt::Password.new(user.reload.password_digest).is_password?('new-password')
15
+ end
16
+
17
+ test 'correct password must be provided to update password' do
18
+ user = FactoryBot.create :user
19
+
20
+ acting_as(user).put password_update_path, params: {
21
+ current_password: 'wrong-password',
22
+ password: 'new-password',
23
+ password_confirmation: 'new-password'
24
+ }
25
+
26
+ assert_response :unprocessable_entity
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ require 'test_helper'
2
+
3
+ class ProfileTest < ActionDispatch::IntegrationTest
4
+ test 'profile page is displayed' do
5
+ user = FactoryBot.create :user
6
+
7
+ acting_as(user).get profile_edit_path
8
+
9
+ assert_response :success
10
+ end
11
+
12
+ test 'profile information can be updated' do
13
+ user = FactoryBot.create :user
14
+
15
+ acting_as(user).patch profile_edit_path, params: {
16
+ name: 'Test User',
17
+ email: 'test@example.com'
18
+ }
19
+
20
+ assert_redirected_to profile_edit_path
21
+
22
+ user.reload
23
+
24
+ assert_equal 'Test User', user.name
25
+ assert_equal 'test@example.com', user.email
26
+ end
27
+
28
+ test 'user can delete their account' do
29
+ user = FactoryBot.create :user
30
+
31
+ acting_as(user).delete profile_destroy_path, params: {
32
+ password: 'password'
33
+ }
34
+
35
+ assert_redirected_to '/'
36
+
37
+ assert_guest
38
+ assert_raise(ActiveRecord::RecordNotFound) { user.reload }
39
+ end
40
+
41
+ test 'correct password must be provided to delete account' do
42
+ user = FactoryBot.create :user
43
+
44
+ acting_as(user).delete profile_destroy_path, params: {
45
+ password: 'wrong-password'
46
+ }
47
+
48
+ assert_response :unprocessable_entity
49
+ assert_not_nil user.reload
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ require_relative '../config/environment'
3
+ require 'rails/test_help'
4
+
5
+ module ActiveSupport
6
+ class TestCase
7
+ # Run tests in parallel with specified workers
8
+ parallelize(workers: :number_of_processors)
9
+
10
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
11
+ fixtures :all
12
+
13
+ # Add more helper methods to be used by all tests here...
14
+ def acting_as(user)
15
+ post login_path, params: { email: user.email, password: 'password' }
16
+
17
+ self
18
+ end
19
+
20
+ def current_auth
21
+ Auth.new('web', session)
22
+ end
23
+
24
+ def assert_authenticated
25
+ assert is_authenticated, 'The user is not authenticated'
26
+ end
27
+
28
+ def assert_guest
29
+ assert_not is_authenticated, 'The user is authenticated'
30
+ end
31
+
32
+ private
33
+
34
+ def is_authenticated
35
+ current_auth.check?
36
+ end
37
+ end
38
+ end
@@ -1,20 +1,19 @@
1
- <div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
2
- <div @click="open = ! open">
3
- <%= trigger %>
4
- </div>
5
-
6
- <div x-show="open"
7
- x-transition:enter="transition ease-out duration-200"
8
- x-transition:enter-start="opacity-0 scale-95"
9
- x-transition:enter-end="opacity-100 scale-100"
10
- x-transition:leave="transition ease-in duration-75"
11
- x-transition:leave-start="opacity-100 scale-100"
12
- x-transition:leave-end="opacity-0 scale-95"
13
- class="absolute z-50 mt-2 <%= @width %> rounded-md shadow-lg <%= @alignment_classes %>"
14
- style="display: none;"
15
- @click="open = false">
16
- <div class="rounded-md ring-1 ring-black ring-opacity-5 <%= @content_classes %>">
17
- <%= content %>
18
- </div>
1
+ <div class="relative" x-data="{ open: false }" x-on:click.outside="open = false" x-on:close.stop="open = false">
2
+ <div x-on:click="open = ! open">
3
+ <%= trigger %>
4
+ </div>
5
+ <div x-show="open"
6
+ x-transition:enter="transition ease-out duration-200"
7
+ x-transition:enter-start="opacity-0 scale-95"
8
+ x-transition:enter-end="opacity-100 scale-100"
9
+ x-transition:leave="transition ease-in duration-75"
10
+ x-transition:leave-start="opacity-100 scale-100"
11
+ x-transition:leave-end="opacity-0 scale-95"
12
+ class="absolute z-50 mt-2 <%= @width %> rounded-md shadow-lg <%= @alignment_classes %>"
13
+ style="display: none;"
14
+ x-on:click="open = false">
15
+ <div class="rounded-md ring-1 ring-black ring-opacity-5 <%= @content_classes %>">
16
+ <%= content %>
19
17
  </div>
18
+ </div>
20
19
  </div>
@@ -1,62 +1,58 @@
1
1
  <div
2
- x-data="{
3
- show: <%= @show %>,
4
- focusables() {
5
- // All focusable element types...
6
- let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
7
- return [...$el.querySelectorAll(selector)]
8
- // All non-disabled elements...
9
- .filter(el => ! el.hasAttribute('disabled'))
10
- },
11
- firstFocusable() { return this.focusables()[0] },
12
- lastFocusable() { return this.focusables().slice(-1)[0] },
13
- nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
14
- prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
15
- nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
16
- prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
17
- }"
18
- x-init="$watch('show', value => {
19
- if (value) {
20
- document.body.classList.add('overflow-y-hidden');
21
- <%= @attributes.has_key?(:focusable) ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' %>
22
- } else {
23
- document.body.classList.remove('overflow-y-hidden');
24
- }
25
- })"
26
- x-on:open-modal.window="$event.detail == '<%= @name %>' ? show = true : null"
27
- x-on:close-modal.window="$event.detail == '<%= @name %>' ? show = false : null"
28
- x-on:close.stop="show = false"
29
- x-on:keydown.escape.window="show = false"
30
- x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
31
- x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
2
+ x-data="{
3
+ show: <%= @show %>,
4
+ focusables() {
5
+ // All focusable element types...
6
+ let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
7
+ return [...$el.querySelectorAll(selector)]
8
+ // All non-disabled elements...
9
+ .filter(el => ! el.hasAttribute('disabled'))
10
+ },
11
+ firstFocusable() { return this.focusables()[0] },
12
+ lastFocusable() { return this.focusables().slice(-1)[0] },
13
+ nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
14
+ prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
15
+ nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
16
+ prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
17
+ }"
18
+ x-init="$watch('show', value => {
19
+ if (value) {
20
+ document.body.classList.add('overflow-y-hidden')
21
+ <%= @attributes.has_key?(:focusable) ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' %>
22
+ } else {
23
+ document.body.classList.remove('overflow-y-hidden')
24
+ }
25
+ })"
26
+ x-on:open-modal.window="$event.detail == '<%= @name %>' ? show = true : null"
27
+ x-on:close-modal.window="$event.detail == '<%= @name %>' ? show = false : null"
28
+ x-on:close.stop="show = false"
29
+ x-on:keydown.escape.window="show = false"
30
+ x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
31
+ x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
32
+ x-show="show"
33
+ class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
34
+ style="display: <%= @show ? 'block' : 'none' %>">
35
+ <div
32
36
  x-show="show"
33
- class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
34
- style="display: <%= @show ? 'block' : 'none' %>;"
35
- >
36
- <div
37
- x-show="show"
38
- class="fixed inset-0 transform transition-all"
39
- x-on:click="show = false"
40
- x-transition:enter="ease-out duration-300"
41
- x-transition:enter-start="opacity-0"
42
- x-transition:enter-end="opacity-100"
43
- x-transition:leave="ease-in duration-200"
44
- x-transition:leave-start="opacity-100"
45
- x-transition:leave-end="opacity-0"
46
- >
47
- <div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
48
- </div>
49
-
50
- <div
51
- x-show="show"
52
- class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full <%= @max_width %> sm:mx-auto"
53
- x-transition:enter="ease-out duration-300"
54
- x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
55
- x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
56
- x-transition:leave="ease-in duration-200"
57
- x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
58
- x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
59
- >
60
- <%= content %>
61
- </div>
37
+ class="fixed inset-0 transform transition-all"
38
+ x-on:click="show = false"
39
+ x-transition:enter="ease-out duration-300"
40
+ x-transition:enter-start="opacity-0"
41
+ x-transition:enter-end="opacity-100"
42
+ x-transition:leave="ease-in duration-200"
43
+ x-transition:leave-start="opacity-100"
44
+ x-transition:leave-end="opacity-0">
45
+ <div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
46
+ </div>
47
+ <div
48
+ x-show="show"
49
+ class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full <%= @max_width %> sm:mx-auto"
50
+ x-transition:enter="ease-out duration-300"
51
+ x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
52
+ x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
53
+ x-transition:leave="ease-in duration-200"
54
+ x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
55
+ x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
56
+ <%= content %>
57
+ </div>
62
58
  </div>
@@ -1,3 +1,4 @@
1
1
  class ApplicationController < ActionController::Base
2
+ include SetCurrentAuth
2
3
  include Authenticate
3
4
  end
@@ -1,5 +1,8 @@
1
1
  class Auth::AuthenticatedSessionController < ApplicationController
2
- skip_authentication only: %i[new create]
2
+ include RedirectIfAuthenticated
3
+
4
+ skip_authenticate only: %i[new create]
5
+ skip_redirect_if_authenticated only: %i[destroy]
3
6
 
4
7
  layout 'guest'
5
8
 
@@ -12,18 +15,18 @@ class Auth::AuthenticatedSessionController < ApplicationController
12
15
  def create
13
16
  @form = Auth::LoginForm.new params.permit(:email, :password)
14
17
 
15
- user = @form.authenticate
16
-
17
- return render 'auth/login', status: :unprocessable_entity if user.nil?
18
+ @form.authenticate
18
19
 
19
- login user
20
+ return render 'auth/login', status: :unprocessable_entity if Current.auth.user.nil?
20
21
 
21
22
  redirect_to dashboard_path
22
23
  end
23
24
 
24
25
  def destroy
25
- logout
26
+ Current.auth.logout
27
+
28
+ reset_session
26
29
 
27
- redirect_to login_path
30
+ redirect_to '/'
28
31
  end
29
32
  end
@@ -1,5 +1,7 @@
1
1
  class Auth::NewPasswordController < ApplicationController
2
- skip_authentication
2
+ include RedirectIfAuthenticated
3
+
4
+ skip_authenticate
3
5
 
4
6
  layout 'guest'
5
7
 
@@ -1,5 +1,7 @@
1
1
  class Auth::PasswordResetLinkController < ApplicationController
2
- skip_authentication
2
+ include RedirectIfAuthenticated
3
+
4
+ skip_authenticate
3
5
 
4
6
  layout 'guest'
5
7
 
@@ -1,5 +1,7 @@
1
1
  class Auth::RegisteredUserController < ApplicationController
2
- skip_authentication
2
+ include RedirectIfAuthenticated
3
+
4
+ skip_authenticate
3
5
 
4
6
  layout 'guest'
5
7
 
@@ -16,7 +18,7 @@ class Auth::RegisteredUserController < ApplicationController
16
18
 
17
19
  user = User.create(name: @form.name, email: @form.email, password: @form.password)
18
20
 
19
- login user
21
+ Current.auth.login user
20
22
 
21
23
  redirect_to dashboard_path
22
24
  end
@@ -2,33 +2,18 @@ module Authenticate
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- before_action :authenticate_user!
5
+ before_action :authenticate!
6
6
  end
7
7
 
8
8
  class_methods do
9
- def skip_authentication(**options)
10
- skip_before_action :authenticate_user!, **options
9
+ def skip_authenticate(**options)
10
+ skip_before_action :authenticate!, **options
11
11
  end
12
12
  end
13
13
 
14
14
  private
15
15
 
16
- def authenticate_user!
17
- if user = User.find_by(id: session[:user_id])
18
- Current.user = user
19
- else
20
- redirect_to login_path
21
- end
22
- end
23
-
24
- def login(user)
25
- Current.user = user
26
- reset_session
27
- session[:user_id] = user.id
28
- end
29
-
30
- def logout
31
- Current.user = nil
32
- reset_session
16
+ def authenticate!
17
+ redirect_to login_path unless Current.auth.check?
33
18
  end
34
19
  end
@@ -0,0 +1,19 @@
1
+ module RedirectIfAuthenticated
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :redirect_if_authenticated!
6
+ end
7
+
8
+ class_methods do
9
+ def skip_redirect_if_authenticated(**options)
10
+ skip_before_action :redirect_if_authenticated!, **options
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def redirect_if_authenticated!
17
+ redirect_to dashboard_path if Current.auth.check?
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module SetCurrentAuth
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action do
6
+ Current.auth = Auth.new('web', session)
7
+ end
8
+ end
9
+ end
@@ -4,7 +4,7 @@ class PasswordController < ApplicationController
4
4
 
5
5
  return render partial: 'profile/partials/update_password_form', status: :unprocessable_entity if @update_password_form.invalid?
6
6
 
7
- Current.user.update(password: @update_password_form.password)
7
+ Current.auth.user.update(password: @update_password_form.password)
8
8
 
9
9
  redirect_back_or_to profile_edit_path, flash: { status: 'password-updated' }
10
10
  end