kaze 0.8.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kaze/commands/install_command.rb +3 -1
  3. data/lib/kaze/commands/installs_hotwire_stack.rb +2 -2
  4. data/lib/kaze/commands/installs_inertia_stacks.rb +7 -7
  5. data/lib/kaze/version.rb +1 -1
  6. data/stubs/default/app/forms/update_profile_information_form.rb +8 -0
  7. data/stubs/default/app/mailers/application_mailer.rb +1 -1
  8. data/stubs/default/app/mailers/user_mailer.rb +16 -1
  9. data/stubs/default/app/models/concerns/can_reset_password.rb +1 -1
  10. data/stubs/default/app/models/concerns/must_verify_email.rb +15 -0
  11. data/stubs/default/app/models/user.rb +1 -0
  12. data/stubs/default/app/views/user_mailer/reset_password.html.erb +2 -2
  13. data/stubs/default/app/views/user_mailer/verify_email.html.erb +33 -0
  14. data/stubs/default/config/routes.rb +4 -0
  15. data/stubs/default/db/migrate/20240101000000_create_users.rb +1 -0
  16. data/stubs/default/test/factories/users.rb +5 -0
  17. data/stubs/default/test/integration/auth/authentication_test.rb +4 -6
  18. data/stubs/default/test/integration/auth/email_verification_test.rb +40 -0
  19. data/stubs/default/test/integration/auth/password_reset_test.rb +3 -3
  20. data/stubs/default/test/integration/password_update_test.rb +2 -2
  21. data/stubs/default/test/integration/profile_test.rb +16 -4
  22. data/stubs/hotwire/app/components/modal_component.rb +5 -5
  23. data/stubs/hotwire/app/controllers/auth/authenticated_session_controller.rb +3 -2
  24. data/stubs/hotwire/app/controllers/auth/email_verification_notification_controller.rb +21 -0
  25. data/stubs/hotwire/app/controllers/auth/new_password_controller.rb +2 -2
  26. data/stubs/hotwire/app/controllers/auth/password_reset_link_controller.rb +1 -1
  27. data/stubs/hotwire/app/controllers/auth/registered_user_controller.rb +6 -2
  28. data/stubs/hotwire/app/controllers/auth/verified_email_controller.rb +23 -0
  29. data/stubs/hotwire/app/controllers/concerns/authenticate.rb +10 -0
  30. data/stubs/hotwire/app/controllers/concerns/validate_signature.rb +17 -0
  31. data/stubs/hotwire/app/controllers/password_controller.rb +3 -1
  32. data/stubs/hotwire/app/controllers/profile_controller.rb +5 -3
  33. data/stubs/hotwire/app/views/auth/verify_email.html.erb +23 -0
  34. data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +1 -1
  35. data/stubs/hotwire/app/views/profile/partials/_update_profile_information_form.html.erb +18 -0
  36. data/stubs/inertia-common/app/controllers/auth/authenticated_session_controller.rb +3 -3
  37. data/stubs/inertia-common/app/controllers/auth/email_verification_notification_controller.rb +21 -0
  38. data/stubs/inertia-common/app/controllers/auth/new_password_controller.rb +2 -3
  39. data/stubs/inertia-common/app/controllers/auth/password_reset_link_controller.rb +3 -3
  40. data/stubs/inertia-common/app/controllers/auth/registered_user_controller.rb +6 -2
  41. data/stubs/inertia-common/app/controllers/auth/verified_email_controller.rb +23 -0
  42. data/stubs/inertia-common/app/controllers/concerns/authenticate.rb +10 -0
  43. data/stubs/inertia-common/app/controllers/concerns/validate_signature.rb +17 -0
  44. data/stubs/inertia-common/app/controllers/password_controller.rb +3 -1
  45. data/stubs/inertia-common/app/controllers/profile_controller.rb +7 -4
  46. data/stubs/inertia-common/test/integration/password_update_test.rb +2 -2
  47. data/stubs/inertia-common/test/integration/profile_test.rb +16 -4
  48. data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +1 -4
  49. data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +3 -5
  50. data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +4 -25
  51. data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +1 -4
  52. data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +1 -4
  53. data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +3 -5
  54. data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +3 -5
  55. data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +3 -5
  56. data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +1 -7
  57. data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +14 -52
  58. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +4 -7
  59. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +10 -24
  60. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +2 -5
  61. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +2 -5
  62. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/VerifyEmail.tsx +47 -0
  63. data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +2 -8
  64. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +10 -10
  65. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +10 -20
  66. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +10 -18
  67. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +35 -12
  68. data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +1 -2
  69. data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +1 -5
  70. data/stubs/inertia-react-ts/app/javascript/entrypoints/bootstrap.ts +3 -3
  71. data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +4 -4
  72. data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +8 -8
  73. data/stubs/inertia-react-ts/config/tailwind.config.js +1 -6
  74. data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +3 -1
  75. data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +1 -4
  76. data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +3 -13
  77. data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +10 -45
  78. data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +2 -6
  79. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +4 -11
  80. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +2 -10
  81. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +1 -5
  82. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +2 -22
  83. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/VerifyEmail.vue +50 -0
  84. data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +3 -11
  85. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +2 -10
  86. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +5 -9
  87. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +2 -9
  88. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +26 -15
  89. data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +2 -5
  90. data/stubs/inertia-vue-ts/app/javascript/entrypoints/application.ts +27 -23
  91. data/stubs/inertia-vue-ts/app/javascript/entrypoints/bootstrap.ts +3 -3
  92. data/stubs/inertia-vue-ts/app/javascript/types/global.d.ts +7 -7
  93. data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +8 -8
  94. data/stubs/inertia-vue-ts/config/tailwind.config.js +1 -6
  95. metadata +14 -2
@@ -0,0 +1,23 @@
1
+ <div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
2
+ Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.
3
+ </div>
4
+ <% if flash[:status] == 'verification-link-sent' %>
5
+ <div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400">
6
+ A new verification link has been sent to the email address you provided during registration.
7
+ </div>
8
+ <% end %>
9
+ <div class="mt-4 flex items-center justify-between">
10
+ <form method="POST" action="<%= verification_send_path %>">
11
+ <div>
12
+ <%= render(PrimaryButtonComponent.new) do %>
13
+ Resend Verification Email
14
+ <% end %>
15
+ </div>
16
+ </form>
17
+ <form method="POST" action="<%= logout_path %>">
18
+ <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
19
+ <button type="submit" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
20
+ Log Out
21
+ </button>
22
+ </form>
23
+ </div>
@@ -10,7 +10,7 @@
10
10
  <%= render(DangerButtonComponent.new({"x-data": "", "x-on:click.prevent": "$dispatch('open-modal', 'confirm-user-deletion')"})) do %>
11
11
  Delete Account
12
12
  <% end %>
13
- <%= render(ModalComponent.new({ :name => "confirm-user-deletion", :show => @delete_user_form.error_messages.has_key?(:password), :focusable => true })) do %>
13
+ <%= render(ModalComponent.new({ name: "confirm-user-deletion", show: @delete_user_form.error_messages.has_key?(:password), focusable: true })) do %>
14
14
  <turbo-frame id="delete_user_form">
15
15
  <%= form_with model: @delete_user_form, url: profile_destroy_path, method: "delete", class: "p-6" do %>
16
16
  <h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
@@ -8,6 +8,9 @@
8
8
  Update your account's profile information and email address.
9
9
  </p>
10
10
  </header>
11
+ <form id="send-verification" method="post" action="<%= verification_send_path %>">
12
+ <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
13
+ </form>
11
14
  <%= form_with model: @update_profile_information_form, url: profile_update_path, method: "patch", class: "mt-6 space-y-6" do %>
12
15
  <!-- Name -->
13
16
  <div>
@@ -20,6 +23,21 @@
20
23
  <%= render(InputLabelComponent.new({ for: "email", value: "Email" })) %>
21
24
  <%= render(TextInputComponent.new({ id: "email", class: "block mt-1 w-full", type: "email", name: "email", value: @update_profile_information_form.email, required: true, autocomplete: "username" })) %>
22
25
  <%= render(InputErrorComponent.new({ class: "mt-2", message: @update_profile_information_form.error_messages[:email] })) %>
26
+ <% if User.include?(MustVerifyEmail) && !Current.auth.user.has_verified_email? %>
27
+ <div>
28
+ <p class="text-sm mt-2 text-gray-800 dark:text-gray-200">
29
+ Your email address is unverified.
30
+ <button form="send-verification" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
31
+ Click here to re-send the verification email.
32
+ </button>
33
+ </p>
34
+ <% if flash[:status] == 'verification-link-sent' %>
35
+ <p class="mt-2 font-medium text-sm text-green-600 dark:text-green-400">
36
+ A new verification link has been sent to your email address.
37
+ </p>
38
+ <% end %>
39
+ </div>
40
+ <% end %>
23
41
  </div>
24
42
  <div class="flex items-center gap-4">
25
43
  <%= render(PrimaryButtonComponent.new) do %>
@@ -2,17 +2,17 @@ class Auth::AuthenticatedSessionController < ApplicationController
2
2
  include RedirectIfAuthenticated
3
3
 
4
4
  skip_authenticate only: %i[new create]
5
- skip_redirect_if_authenticated only: %i[destroy]
5
+ skip_redirect_if_authenticated only: :destroy
6
+ skip_ensure_email_is_verified only: :destroy
6
7
 
7
8
  def new
8
9
  render inertia: 'Auth/Login', props: {
9
- canResetPassword: true,
10
10
  status: flash[:status]
11
11
  }
12
12
  end
13
13
 
14
14
  def create
15
- form = Auth::LoginForm.new params.permit(:email, :password, :remember)
15
+ form = Auth::LoginForm.new(params.permit(:email, :password, :remember))
16
16
 
17
17
  form.authenticate
18
18
 
@@ -0,0 +1,21 @@
1
+ class Auth::EmailVerificationNotificationController < ApplicationController
2
+ skip_ensure_email_is_verified
3
+
4
+ before_action { redirect_to dashboard_path unless User.include?(MustVerifyEmail) }
5
+
6
+ def new
7
+ return redirect_to dashboard_path if Current.auth.user.has_verified_email?
8
+
9
+ render inertia: 'Auth/VerifyEmail', props: {
10
+ status: flash[:status]
11
+ }
12
+ end
13
+
14
+ def create
15
+ return redirect_to dashboard_path if Current.auth.user.has_verified_email?
16
+
17
+ Current.auth.user.send_email_verification_notification
18
+
19
+ redirect_back_or_to verification_notice_path, flash: { status: 'verification-link-sent' }
20
+ end
21
+ end
@@ -10,11 +10,10 @@ class Auth::NewPasswordController < ApplicationController
10
10
  end
11
11
 
12
12
  def create
13
- form = Auth::NewPasswordForm.new params.permit(:token, :password, :password_confirmation)
13
+ form = Auth::NewPasswordForm.new(params.permit(:token, :password, :password_confirmation))
14
14
 
15
15
  return redirect_to login_path, flash: { status: 'Your password has been reset.' } if form.reset?
16
16
 
17
- redirect_back_or_to password_reset_path(token: form.token),
18
- inertia: { errors: form.error_messages }
17
+ redirect_back_or_to password_reset_path(token: form.token), inertia: { errors: form.error_messages }
19
18
  end
20
19
  end
@@ -10,10 +10,10 @@ class Auth::PasswordResetLinkController < ApplicationController
10
10
  end
11
11
 
12
12
  def create
13
- form = Auth::SendPasswordResetLinkForm.new params.permit(:email)
13
+ form = Auth::SendPasswordResetLinkForm.new(params.permit(:email))
14
14
 
15
- return redirect_back_or_to password_request_path, inertia: { errors: form.error_messages } unless form.send_reset_link?
15
+ return redirect_back_or_to password_request_path, flash: { status: 'We have emailed your password reset link.' } if form.send_reset_link?
16
16
 
17
- redirect_back_or_to password_request_path, flash: { status: 'We have emailed your password reset link.' }
17
+ redirect_back_or_to password_request_path, inertia: { errors: form.error_messages }
18
18
  end
19
19
  end
@@ -8,13 +8,17 @@ class Auth::RegisteredUserController < ApplicationController
8
8
  end
9
9
 
10
10
  def create
11
- form = Auth::RegisterForm.new params.permit(:name, :email, :password, :password_confirmation)
11
+ form = Auth::RegisterForm.new(params.permit(:name, :email, :password, :password_confirmation))
12
12
 
13
13
  return redirect_to register_path, inertia: { errors: form.error_messages } if form.invalid?
14
14
 
15
15
  user = User.create(name: form.name, email: form.email, password: form.password)
16
16
 
17
- Current.auth.login user
17
+ if User.include?(MustVerifyEmail) && !user.has_verified_email?
18
+ user.send_email_verification_notification
19
+ end
20
+
21
+ Current.auth.login(user)
18
22
 
19
23
  redirect_to dashboard_path
20
24
  end
@@ -0,0 +1,23 @@
1
+ class Auth::VerifiedEmailController < ApplicationController
2
+ include ValidateSignature
3
+
4
+ skip_ensure_email_is_verified
5
+
6
+ before_action { redirect_to dashboard_path unless User.include?(MustVerifyEmail) }
7
+
8
+ def create
9
+ return redirect_to dashboard_path(verified: '1') if Current.auth.user.has_verified_email?
10
+
11
+ Current.auth.user.mark_email_as_verified if email_verification_request_is_authorized?
12
+
13
+ redirect_to dashboard_path(verified: '1')
14
+ end
15
+
16
+ private
17
+
18
+ def email_verification_request_is_authorized?
19
+ return false if !ActiveSupport::SecurityUtils.secure_compare Current.auth.user.id.to_s, params[:id]
20
+
21
+ ActiveSupport::SecurityUtils.secure_compare Digest::SHA1.hexdigest(Current.auth.user.email), params[:hash]
22
+ end
23
+ end
@@ -3,11 +3,17 @@ module Authenticate
3
3
 
4
4
  included do
5
5
  before_action :authenticate!
6
+ before_action :ensure_email_is_verified! if User.include?(MustVerifyEmail)
6
7
  end
7
8
 
8
9
  class_methods do
9
10
  def skip_authenticate(**options)
10
11
  skip_before_action :authenticate!, **options
12
+ skip_ensure_email_is_verified(**options)
13
+ end
14
+
15
+ def skip_ensure_email_is_verified(**options)
16
+ skip_before_action :ensure_email_is_verified!, **options if User.include?(MustVerifyEmail)
11
17
  end
12
18
  end
13
19
 
@@ -16,4 +22,8 @@ module Authenticate
16
22
  def authenticate!
17
23
  redirect_to login_path unless Current.auth.check?
18
24
  end
25
+
26
+ def ensure_email_is_verified!
27
+ redirect_to verification_notice_path unless Current.auth.user && Current.auth.user.has_verified_email?
28
+ end
19
29
  end
@@ -0,0 +1,17 @@
1
+ module ValidateSignature
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action do
6
+ render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found unless has_valid_signature?
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def has_valid_signature?
13
+ request.original_url.split('?')[0] == ActiveSupport::MessageVerifier.new(ENV.fetch('RAILS_MASTER_KEY', '')).verify(params[:signature])
14
+ rescue
15
+ false
16
+ end
17
+ end
@@ -1,6 +1,8 @@
1
1
  class PasswordController < ApplicationController
2
+ skip_ensure_email_is_verified
3
+
2
4
  def update
3
- form = UpdatePasswordForm.new params.permit(:current_password, :password, :password_confirmation)
5
+ form = UpdatePasswordForm.new(params.permit(:current_password, :password, :password_confirmation))
4
6
 
5
7
  return redirect_to profile_edit_path, inertia: { errors: form.error_messages } if form.invalid?
6
8
 
@@ -1,22 +1,25 @@
1
1
  class ProfileController < ApplicationController
2
+ skip_ensure_email_is_verified
3
+
2
4
  def edit
3
5
  render inertia: 'Profile/Edit', props: {
4
- status: session[:status]
6
+ mustVerifyEmail: User.include?(MustVerifyEmail),
7
+ status: flash[:status]
5
8
  }
6
9
  end
7
10
 
8
11
  def update
9
- form = UpdateProfileInformationForm.new params.permit(:name, :email)
12
+ form = UpdateProfileInformationForm.new(params.permit(:name, :email))
10
13
 
11
14
  return redirect_to profile_edit_path, inertia: { errors: form.error_messages } if form.invalid?
12
15
 
13
- Current.auth.user.update(name: form.name, email: form.email)
16
+ form.update
14
17
 
15
18
  redirect_to profile_edit_path
16
19
  end
17
20
 
18
21
  def destroy
19
- form = DeleteUserForm.new params.permit(:password)
22
+ form = DeleteUserForm.new(params.permit(:password))
20
23
 
21
24
  return redirect_back_or_to profile_edit_path, inertia: { errors: form.error_messages } if form.invalid?
22
25
 
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class PasswordUpdateTest < ActionDispatch::IntegrationTest
4
4
  test 'password can be updated' do
5
- user = FactoryBot.create :user
5
+ user = FactoryBot.create(:user)
6
6
 
7
7
  acting_as(user).put password_update_path, params: {
8
8
  current_password: 'password',
@@ -15,7 +15,7 @@ class PasswordUpdateTest < ActionDispatch::IntegrationTest
15
15
  end
16
16
 
17
17
  test 'correct password must be provided to update password' do
18
- user = FactoryBot.create :user
18
+ user = FactoryBot.create(:user)
19
19
 
20
20
  acting_as(user).put password_update_path, params: {
21
21
  current_password: 'wrong-password',
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class ProfileTest < ActionDispatch::IntegrationTest
4
4
  test 'profile page is displayed' do
5
- user = FactoryBot.create :user
5
+ user = FactoryBot.create(:user)
6
6
 
7
7
  acting_as(user).get profile_edit_path
8
8
 
@@ -10,7 +10,7 @@ class ProfileTest < ActionDispatch::IntegrationTest
10
10
  end
11
11
 
12
12
  test 'profile information can be updated' do
13
- user = FactoryBot.create :user
13
+ user = FactoryBot.create(:user)
14
14
 
15
15
  acting_as(user).patch profile_edit_path, params: {
16
16
  name: 'Test User',
@@ -25,8 +25,20 @@ class ProfileTest < ActionDispatch::IntegrationTest
25
25
  assert_equal 'test@example.com', user.email
26
26
  end
27
27
 
28
+ test 'email verification status is unchanged when the email address is unchanged' do
29
+ user = FactoryBot.create(:user)
30
+
31
+ acting_as(user).patch profile_edit_path, params: {
32
+ name: 'Test User',
33
+ email: user.email
34
+ }
35
+
36
+ assert_redirected_to profile_edit_path
37
+ assert_not user.reload.email_verified_at.blank?
38
+ end
39
+
28
40
  test 'user can delete their account' do
29
- user = FactoryBot.create :user
41
+ user = FactoryBot.create(:user)
30
42
 
31
43
  acting_as(user).delete profile_destroy_path, params: {
32
44
  password: 'password'
@@ -39,7 +51,7 @@ class ProfileTest < ActionDispatch::IntegrationTest
39
51
  end
40
52
 
41
53
  test 'correct password must be provided to delete account' do
42
- user = FactoryBot.create :user
54
+ user = FactoryBot.create(:user)
43
55
 
44
56
  acting_as(user).delete profile_destroy_path, params: {
45
57
  password: 'wrong-password'
@@ -1,9 +1,6 @@
1
1
  import { InputHTMLAttributes } from 'react'
2
2
 
3
- export default function Checkbox({
4
- className = '',
5
- ...props
6
- }: InputHTMLAttributes<HTMLInputElement>) {
3
+ export default function Checkbox({ className = '', ...props }: InputHTMLAttributes<HTMLInputElement>) {
7
4
  return (
8
5
  <input
9
6
  {...props}
@@ -9,11 +9,9 @@ export default function DangerButton({
9
9
  return (
10
10
  <button
11
11
  {...props}
12
- className={
13
- `inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 ${
14
- disabled && 'opacity-25'
15
- } ${className}`
16
- }
12
+ className={`inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 ${
13
+ disabled && 'opacity-25'
14
+ } ${className}`}
17
15
  disabled={disabled}
18
16
  >
19
17
  {children}
@@ -1,12 +1,4 @@
1
- import {
2
- useState,
3
- createContext,
4
- useContext,
5
- Fragment,
6
- PropsWithChildren,
7
- Dispatch,
8
- SetStateAction,
9
- } from 'react'
1
+ import { useState, createContext, useContext, Fragment, PropsWithChildren, Dispatch, SetStateAction } from 'react'
10
2
  import { Link, InertiaLinkProps } from '@inertiajs/react'
11
3
  import { Transition } from '@headlessui/react'
12
4
 
@@ -41,12 +33,7 @@ const Trigger = ({ children }: PropsWithChildren) => {
41
33
  <>
42
34
  <div onClick={toggleOpen}>{children}</div>
43
35
 
44
- {open && (
45
- <div
46
- className="fixed inset-0 z-40"
47
- onClick={() => setOpen(false)}
48
- ></div>
49
- )}
36
+ {open && <div className="fixed inset-0 z-40" onClick={() => setOpen(false)}></div>}
50
37
  </>
51
38
  )
52
39
  }
@@ -93,22 +80,14 @@ const Content = ({
93
80
  className={`absolute z-50 mt-2 rounded-md shadow-lg ${alignmentClasses} ${widthClasses}`}
94
81
  onClick={() => setOpen(false)}
95
82
  >
96
- <div
97
- className={`rounded-md ring-1 ring-black ring-opacity-5 ${contentClasses}`}
98
- >
99
- {children}
100
- </div>
83
+ <div className={`rounded-md ring-1 ring-black ring-opacity-5 ${contentClasses}`}>{children}</div>
101
84
  </div>
102
85
  </Transition>
103
86
  </>
104
87
  )
105
88
  }
106
89
 
107
- const DropdownLink = ({
108
- className = '',
109
- children,
110
- ...props
111
- }: InertiaLinkProps) => {
90
+ const DropdownLink = ({ className = '', children, ...props }: InertiaLinkProps) => {
112
91
  return (
113
92
  <Link
114
93
  {...props}
@@ -6,10 +6,7 @@ export default function InputError({
6
6
  ...props
7
7
  }: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
8
8
  return message ? (
9
- <p
10
- {...props}
11
- className={`text-sm text-red-600 dark:text-red-400 ${className}`}
12
- >
9
+ <p {...props} className={`text-sm text-red-600 dark:text-red-400 ${className}`}>
13
10
  {message}
14
11
  </p>
15
12
  ) : null
@@ -7,10 +7,7 @@ export default function InputLabel({
7
7
  ...props
8
8
  }: LabelHTMLAttributes<HTMLLabelElement> & { value?: string }) {
9
9
  return (
10
- <label
11
- {...props}
12
- className={`block font-medium text-sm text-gray-700 dark:text-gray-300 ${className}`}
13
- >
10
+ <label {...props} className={`block font-medium text-sm text-gray-700 dark:text-gray-300 ${className}`}>
14
11
  {value ? value : children}
15
12
  </label>
16
13
  )
@@ -9,13 +9,11 @@ export default function NavLink({
9
9
  return (
10
10
  <Link
11
11
  {...props}
12
- className={
13
- `inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 transition duration-150 ease-in-out focus:outline-none ${
14
- active
12
+ className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 transition duration-150 ease-in-out focus:outline-none ${
13
+ active
15
14
  ? 'border-indigo-400 dark:border-indigo-600 text-gray-900 dark:text-gray-100 focus:border-indigo-700 '
16
15
  : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 '
17
- } ${className}`
18
- }
16
+ } ${className}`}
19
17
  >
20
18
  {children}
21
19
  </Link>
@@ -9,11 +9,9 @@ export default function PrimaryButton({
9
9
  return (
10
10
  <button
11
11
  {...props}
12
- className={
13
- `inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 ${
14
- disabled && 'opacity-25'
15
- } ${className}`
16
- }
12
+ className={`inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 ${
13
+ disabled && 'opacity-25'
14
+ } ${className}`}
17
15
  disabled={disabled}
18
16
  >
19
17
  {children}
@@ -11,11 +11,9 @@ export default function SecondaryButton({
11
11
  <button
12
12
  {...props}
13
13
  type={type}
14
- className={
15
- `inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150 ${
16
- disabled && 'opacity-25'
17
- } ${className}`
18
- }
14
+ className={`inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150 ${
15
+ disabled && 'opacity-25'
16
+ } ${className}`}
19
17
  disabled={disabled}
20
18
  >
21
19
  {children}
@@ -1,10 +1,4 @@
1
- import {
2
- forwardRef,
3
- useEffect,
4
- useImperativeHandle,
5
- useRef,
6
- InputHTMLAttributes,
7
- } from 'react'
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef, InputHTMLAttributes } from 'react'
8
2
 
9
3
  export default forwardRef(function TextInput(
10
4
  {
@@ -12,8 +12,7 @@ export default function Authenticated({
12
12
  header,
13
13
  children,
14
14
  }: PropsWithChildren<{ user: User; header?: ReactNode }>) {
15
- const [showingNavigationDropdown, setShowingNavigationDropdown] =
16
- useState(false)
15
+ const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false)
17
16
 
18
17
  const { pathname = '' } = typeof window !== 'undefined' ? window.location : {}
19
18
 
@@ -30,10 +29,7 @@ export default function Authenticated({
30
29
  </div>
31
30
 
32
31
  <div className="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
33
- <NavLink
34
- href={dashboard_path()}
35
- active={pathname.match(/dashboard/) != null}
36
- >
32
+ <NavLink href={dashboard_path()} active={pathname.match(/dashboard/) != null}>
37
33
  Dashboard
38
34
  </NavLink>
39
35
  </div>
@@ -67,14 +63,8 @@ export default function Authenticated({
67
63
  </Dropdown.Trigger>
68
64
 
69
65
  <Dropdown.Content>
70
- <Dropdown.Link href={profile_edit_path()}>
71
- Profile
72
- </Dropdown.Link>
73
- <Dropdown.Link
74
- href={logout_path()}
75
- method="post"
76
- as="button"
77
- >
66
+ <Dropdown.Link href={profile_edit_path()}>Profile</Dropdown.Link>
67
+ <Dropdown.Link href={logout_path()} method="post" as="button">
78
68
  Log Out
79
69
  </Dropdown.Link>
80
70
  </Dropdown.Content>
@@ -84,32 +74,19 @@ export default function Authenticated({
84
74
 
85
75
  <div className="-me-2 flex items-center sm:hidden">
86
76
  <button
87
- onClick={() =>
88
- setShowingNavigationDropdown(
89
- (previousState) => !previousState,
90
- )
91
- }
77
+ onClick={() => setShowingNavigationDropdown((previousState) => !previousState)}
92
78
  className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
93
79
  >
94
- <svg
95
- className="h-6 w-6"
96
- stroke="currentColor"
97
- fill="none"
98
- viewBox="0 0 24 24"
99
- >
80
+ <svg className="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
100
81
  <path
101
- className={
102
- !showingNavigationDropdown ? 'inline-flex' : 'hidden'
103
- }
82
+ className={!showingNavigationDropdown ? 'inline-flex' : 'hidden'}
104
83
  strokeLinecap="round"
105
84
  strokeLinejoin="round"
106
85
  strokeWidth="2"
107
86
  d="M4 6h16M4 12h16M4 18h16"
108
87
  />
109
88
  <path
110
- className={
111
- showingNavigationDropdown ? 'inline-flex' : 'hidden'
112
- }
89
+ className={showingNavigationDropdown ? 'inline-flex' : 'hidden'}
113
90
  strokeLinecap="round"
114
91
  strokeLinejoin="round"
115
92
  strokeWidth="2"
@@ -121,34 +98,21 @@ export default function Authenticated({
121
98
  </div>
122
99
  </div>
123
100
 
124
- <div
125
- className={
126
- (showingNavigationDropdown ? 'block' : 'hidden') + ' sm:hidden'
127
- }
128
- >
101
+ <div className={(showingNavigationDropdown ? 'block' : 'hidden') + ' sm:hidden'}>
129
102
  <div className="pt-2 pb-3 space-y-1">
130
- <ResponsiveNavLink
131
- href={dashboard_path()}
132
- active={pathname.match(/dashboard/) != null}
133
- >
103
+ <ResponsiveNavLink href={dashboard_path()} active={pathname.match(/dashboard/) != null}>
134
104
  Dashboard
135
105
  </ResponsiveNavLink>
136
106
  </div>
137
107
 
138
108
  <div className="pt-4 pb-1 border-t border-gray-200">
139
109
  <div className="px-4">
140
- <div className="font-medium text-base text-gray-800">
141
- {user.name}
142
- </div>
143
- <div className="font-medium text-sm text-gray-500">
144
- {user.email}
145
- </div>
110
+ <div className="font-medium text-base text-gray-800">{user.name}</div>
111
+ <div className="font-medium text-sm text-gray-500">{user.email}</div>
146
112
  </div>
147
113
 
148
114
  <div className="mt-3 space-y-1">
149
- <ResponsiveNavLink href={profile_edit_path()}>
150
- Profile
151
- </ResponsiveNavLink>
115
+ <ResponsiveNavLink href={profile_edit_path()}>Profile</ResponsiveNavLink>
152
116
  <ResponsiveNavLink method="post" href={logout_path()} as="button">
153
117
  Log Out
154
118
  </ResponsiveNavLink>
@@ -159,9 +123,7 @@ export default function Authenticated({
159
123
 
160
124
  {header && (
161
125
  <header className="bg-white shadow">
162
- <div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
163
- {header}
164
- </div>
126
+ <div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">{header}</div>
165
127
  </header>
166
128
  )}
167
129