kaze 0.4.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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kaze/commands/install_command.rb +25 -13
  3. data/lib/kaze/commands/installs_hotwire_stack.rb +11 -8
  4. data/lib/kaze/commands/installs_inertia_stacks.rb +34 -24
  5. data/lib/kaze/commands/version_command.rb +6 -0
  6. data/lib/kaze/version.rb +1 -1
  7. data/lib/kaze.rb +1 -3
  8. data/stubs/default/app/forms/auth/login_form.rb +2 -8
  9. data/stubs/default/app/forms/auth/new_password_form.rb +1 -1
  10. data/stubs/default/app/forms/update_profile_information_form.rb +1 -1
  11. data/stubs/default/app/mailers/application_mailer.rb +4 -4
  12. data/stubs/default/app/mailers/user_mailer.rb +1 -1
  13. data/stubs/default/app/models/auth.rb +57 -0
  14. data/stubs/default/app/models/current.rb +1 -1
  15. data/stubs/default/app/validators/current_password_validator.rb +1 -1
  16. data/stubs/default/app/validators/email_validator.rb +1 -1
  17. data/stubs/default/app/validators/lowercase_validator.rb +2 -2
  18. data/stubs/default/app/views/layouts/mailer.html.erb +367 -372
  19. data/stubs/default/app/views/layouts/mailer.text.erb +1 -4
  20. data/stubs/default/app/views/user_mailer/reset_password.html.erb +21 -26
  21. data/stubs/default/config/routes.rb +16 -16
  22. data/stubs/default/db/migrate/20240101000001_create_delayed_jobs.rb +1 -1
  23. data/stubs/default/test/factories/users.rb +7 -0
  24. data/stubs/default/test/integration/auth/authentication_test.rb +43 -0
  25. data/stubs/default/test/integration/auth/password_reset_test.rb +41 -0
  26. data/stubs/default/test/integration/auth/registration_test.rb +21 -0
  27. data/stubs/default/test/integration/password_update_test.rb +28 -0
  28. data/stubs/default/test/integration/profile_test.rb +51 -0
  29. data/stubs/default/test/test_helper.rb +38 -0
  30. data/stubs/hotwire/app/components/danger_button_component.rb +1 -1
  31. data/stubs/hotwire/app/components/dropdown_component.html.erb +17 -18
  32. data/stubs/hotwire/app/components/dropdown_component.rb +7 -7
  33. data/stubs/hotwire/app/components/modal_component.html.erb +55 -59
  34. data/stubs/hotwire/app/components/modal_component.rb +6 -6
  35. data/stubs/hotwire/app/components/primary_button_component.rb +1 -1
  36. data/stubs/hotwire/app/components/secondary_button_component.rb +1 -1
  37. data/stubs/hotwire/app/controllers/application_controller.rb +1 -0
  38. data/stubs/hotwire/app/controllers/auth/authenticated_session_controller.rb +12 -9
  39. data/stubs/hotwire/app/controllers/auth/new_password_controller.rb +7 -5
  40. data/stubs/hotwire/app/controllers/auth/password_reset_link_controller.rb +7 -5
  41. data/stubs/hotwire/app/controllers/auth/registered_user_controller.rb +7 -5
  42. data/stubs/hotwire/app/controllers/concerns/authenticate.rb +5 -20
  43. data/stubs/hotwire/app/controllers/concerns/redirect_if_authenticated.rb +19 -0
  44. data/stubs/hotwire/app/controllers/concerns/set_current_auth.rb +9 -0
  45. data/stubs/hotwire/app/controllers/password_controller.rb +3 -3
  46. data/stubs/hotwire/app/controllers/profile_controller.rb +11 -9
  47. data/stubs/hotwire/app/controllers/welcome_controller.rb +1 -1
  48. data/stubs/hotwire/app/javascript/application.js +3 -3
  49. data/stubs/hotwire/app/views/auth/forgot_password.html.erb +12 -17
  50. data/stubs/hotwire/app/views/auth/login.html.erb +0 -9
  51. data/stubs/hotwire/app/views/auth/register.html.erb +0 -13
  52. data/stubs/hotwire/app/views/auth/reset_password.html.erb +0 -7
  53. data/stubs/hotwire/app/views/dashboard/index.html.erb +9 -10
  54. data/stubs/hotwire/app/views/layouts/_navigation.html.erb +77 -87
  55. data/stubs/hotwire/app/views/layouts/application.html.erb +0 -9
  56. data/stubs/hotwire/app/views/layouts/guest.html.erb +0 -6
  57. data/stubs/hotwire/app/views/profile/edit.html.erb +19 -22
  58. data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +32 -42
  59. data/stubs/hotwire/app/views/profile/partials/_update_password_form.html.erb +42 -55
  60. data/stubs/hotwire/app/views/profile/partials/_update_profile_information_form.html.erb +36 -46
  61. data/stubs/hotwire/app/views/welcome/index.html.erb +34 -46
  62. data/stubs/hotwire/config/importmap.rb +3 -3
  63. data/stubs/hotwire/config/tailwind.config.js +2 -2
  64. data/stubs/inertia-common/app/controllers/application_controller.rb +1 -0
  65. data/stubs/inertia-common/app/controllers/auth/authenticated_session_controller.rb +11 -8
  66. data/stubs/inertia-common/app/controllers/auth/new_password_controller.rb +5 -3
  67. data/stubs/inertia-common/app/controllers/auth/password_reset_link_controller.rb +5 -3
  68. data/stubs/inertia-common/app/controllers/auth/registered_user_controller.rb +5 -3
  69. data/stubs/inertia-common/app/controllers/concerns/authenticate.rb +5 -20
  70. data/stubs/inertia-common/app/controllers/concerns/handle_inertia_requests.rb +1 -1
  71. data/stubs/inertia-common/app/controllers/concerns/redirect_if_authenticated.rb +19 -0
  72. data/stubs/inertia-common/app/controllers/concerns/set_current_auth.rb +9 -0
  73. data/stubs/inertia-common/app/controllers/concerns/verify_csrf_token.rb +4 -4
  74. data/stubs/inertia-common/app/controllers/dashboard_controller.rb +1 -1
  75. data/stubs/inertia-common/app/controllers/password_controller.rb +1 -1
  76. data/stubs/inertia-common/app/controllers/profile_controller.rb +7 -5
  77. data/stubs/inertia-common/app/controllers/welcome_controller.rb +2 -2
  78. data/stubs/inertia-common/bin/vite +6 -6
  79. data/stubs/inertia-common/test/integration/password_update_test.rb +28 -0
  80. data/stubs/inertia-common/test/integration/profile_test.rb +51 -0
  81. data/stubs/inertia-react-ts/app/javascript/Components/ApplicationLogo.tsx +13 -9
  82. data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +15 -12
  83. data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +20 -15
  84. data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +119 -87
  85. data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +14 -7
  86. data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +18 -7
  87. data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +60 -60
  88. data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +21 -16
  89. data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +20 -15
  90. data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +19 -14
  91. data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +22 -16
  92. data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +35 -24
  93. data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +157 -117
  94. data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +15 -15
  95. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +52 -49
  96. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +90 -82
  97. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +118 -115
  98. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +63 -60
  99. data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +23 -17
  100. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +31 -27
  101. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +109 -99
  102. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +121 -113
  103. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +76 -69
  104. data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +87 -63
  105. data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +32 -25
  106. data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +0 -4
  107. data/stubs/inertia-react-ts/config/tailwind.config.js +2 -2
  108. data/stubs/inertia-react-ts/vite.config.ts +2 -5
  109. data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +10 -6
  110. data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +18 -18
  111. data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +5 -5
  112. data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +60 -57
  113. data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +9 -9
  114. data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +7 -7
  115. data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +6 -6
  116. data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +84 -74
  117. data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +12 -12
  118. data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +5 -5
  119. data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +12 -12
  120. data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +13 -13
  121. data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +13 -13
  122. data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +168 -136
  123. data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +15 -13
  124. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +56 -49
  125. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +78 -72
  126. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +101 -97
  127. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +71 -68
  128. data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +22 -14
  129. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +34 -30
  130. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +87 -83
  131. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +105 -98
  132. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +69 -59
  133. data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +74 -47
  134. data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +0 -4
  135. data/stubs/inertia-vue-ts/config/tailwind.config.js +2 -2
  136. data/stubs/inertia-vue-ts/vite.config.ts +2 -5
  137. metadata +19 -6
  138. data/stubs/hotwire/bin/vite +0 -27
  139. data/stubs/inertia-common/Procfile.dev +0 -3
  140. /data/stubs/{hotwire → default}/Procfile.dev +0 -0
  141. /data/stubs/hotwire/app/javascript/{alpinejs.js → alpinejs.stub} +0 -0
@@ -1,8 +1,10 @@
1
1
  class Auth::RegisteredUserController < ApplicationController
2
- skip_authentication
2
+ include RedirectIfAuthenticated
3
+
4
+ skip_authenticate
3
5
 
4
6
  def new
5
- render inertia: "Auth/Register"
7
+ render inertia: 'Auth/Register'
6
8
  end
7
9
 
8
10
  def create
@@ -12,7 +14,7 @@ class Auth::RegisteredUserController < ApplicationController
12
14
 
13
15
  user = User.create(name: form.name, email: form.email, password: form.password)
14
16
 
15
- login user
17
+ Current.auth.login user
16
18
 
17
19
  redirect_to dashboard_path
18
20
  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
@@ -3,7 +3,7 @@ module HandleInertiaRequests
3
3
 
4
4
  included do
5
5
  inertia_share do
6
- { auth: { user: Current.user } }
6
+ { auth: { user: Current.auth.get_user } }
7
7
  end
8
8
  end
9
9
  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
@@ -5,20 +5,20 @@ module VerifyCsrfToken
5
5
  before_action :set_csrf_cookie
6
6
 
7
7
  rescue_from ActionController::InvalidAuthenticityToken do
8
- redirect_back fallback_location: "/", notice: "The page expired, please try again."
8
+ redirect_back fallback_location: '/', notice: 'The page expired, please try again.'
9
9
  end
10
10
  end
11
11
 
12
12
  def request_authenticity_tokens
13
- super << request.headers["HTTP_X_XSRF_TOKEN"]
13
+ super << request.headers['HTTP_X_XSRF_TOKEN']
14
14
  end
15
15
 
16
16
  private
17
17
 
18
18
  def set_csrf_cookie
19
- cookies["XSRF-TOKEN"] = {
19
+ cookies['XSRF-TOKEN'] = {
20
20
  value: form_authenticity_token,
21
- same_site: "Strict"
21
+ same_site: 'Strict'
22
22
  }
23
23
  end
24
24
  end
@@ -1,5 +1,5 @@
1
1
  class DashboardController < ApplicationController
2
2
  def index
3
- render inertia: "Dashboard"
3
+ render inertia: 'Dashboard'
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@ class PasswordController < ApplicationController
4
4
 
5
5
  return redirect_to profile_edit_path, inertia: { errors: form.error_messages } if form.invalid?
6
6
 
7
- Current.user.update(password: form.password)
7
+ Current.auth.user.update(password: form.password)
8
8
 
9
9
  redirect_back_or_to profile_edit_path
10
10
  end
@@ -1,6 +1,6 @@
1
1
  class ProfileController < ApplicationController
2
2
  def edit
3
- render inertia: "Profile/Edit", props: {
3
+ render inertia: 'Profile/Edit', props: {
4
4
  status: session[:status]
5
5
  }
6
6
  end
@@ -10,7 +10,7 @@ class ProfileController < ApplicationController
10
10
 
11
11
  return redirect_to profile_edit_path, inertia: { errors: form.error_messages } if form.invalid?
12
12
 
13
- Current.user.update(name: form.name, email: form.email)
13
+ Current.auth.user.update(name: form.name, email: form.email)
14
14
 
15
15
  redirect_to profile_edit_path
16
16
  end
@@ -20,12 +20,14 @@ class ProfileController < ApplicationController
20
20
 
21
21
  return redirect_back_or_to profile_edit_path, inertia: { errors: form.error_messages } if form.invalid?
22
22
 
23
- user = Current.user
23
+ user = Current.auth.user
24
24
 
25
- logout
25
+ Current.auth.logout
26
26
 
27
27
  user.delete
28
28
 
29
- redirect_to "/"
29
+ reset_session
30
+
31
+ redirect_to '/'
30
32
  end
31
33
  end
@@ -1,8 +1,8 @@
1
1
  class WelcomeController < ApplicationController
2
- skip_authentication
2
+ skip_authenticate
3
3
 
4
4
  def index
5
- render inertia: "Welcome", props: {
5
+ render inertia: 'Welcome', props: {
6
6
  railsVersion: Rails.version,
7
7
  rubyVersion: RUBY_DESCRIPTION
8
8
  }
@@ -8,12 +8,12 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
12
 
13
- bundle_binstub = File.expand_path("bundle", __dir__)
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
14
 
15
15
  if File.file?(bundle_binstub)
16
- if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
16
+ if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
17
17
  load(bundle_binstub)
18
18
  else
19
19
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -21,7 +21,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
21
21
  end
22
22
  end
23
23
 
24
- require "rubygems"
25
- require "bundler/setup"
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
26
 
27
- load Gem.bin_path("vite_ruby", "vite")
27
+ load Gem.bin_path('vite_ruby', 'vite')
@@ -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_redirected_to profile_edit_path
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_redirected_to profile_edit_path
49
+ assert_not_nil user.reload
50
+ end
51
+ end
@@ -1,12 +1,16 @@
1
- import { SVGAttributes } from 'react';
1
+ import { SVGAttributes } from 'react'
2
2
 
3
3
  export default function ApplicationLogo(props: SVGAttributes<SVGElement>) {
4
- return (
5
- <svg {...props} viewBox="0 -6 32 32" xmlns="http://www.w3.org/2000/svg">
6
- <g fill="none" fillRule="evenodd">
7
- <path d="M0-6h32v32H0z"/>
8
- <path fill="#c00" fillRule="nonzero" d="M.985 19.636s.422-4.163 3.375-9.087c2.954-4.924 7.99-8.65 12.083-9.017 8.144-.816 15.46 6.485 15.46 6.485s-.24.168-.494.38C23.42 2.49 18.54 5.274 17.005 6.02c-7.033 3.925-4.91 13.616-4.91 13.616H.987zM24.137 2.32c-.45-.182-.9-.35-1.364-.505l.056-.93c.885.254 1.237.423 1.363.493l-.056.943zM22.8 5.304c.45.028.915.084 1.393.183l-.056.872c-.464-.1-.928-.155-1.392-.17l.056-.885zM17.597.913c-.407 0-.815.015-1.223.058l-.268-.83c.465-.056.915-.084 1.35-.084l.282.858h-.14zm.676 5.178c.35-.154.76-.31 1.237-.45l.31.93c-.41.125-.817.294-1.225.49l-.323-.97zm-6.386-3.7c-.366.184-.718.395-1.083.62l-.647-.985c.38-.225.745-.42 1.097-.604l.633.97zm2.883 6.33c.252-.323.548-.646.87-.942l.634.957c-.31.323-.59.647-.83 1L14.77 8.72zm-2.04 4.53c.112-.506.24-1.027.422-1.547l1.012.802c-.14.548-.24 1.097-.295 1.645l-1.14-.9zM6.57 6.57c-.34.35-.662.73-.958 1.11L4.53 6.752c.323-.352.674-.704 1.04-1.055l1 .872zm-4.25 6.286c-.224.52-.52 1.21-.702 1.688L0 13.954c.14-.38.436-1.084.703-1.69l1.618.592zm10.2 3.967l1.518.548c.084.663.21 1.28.337 1.83l-1.688-.605c-.07-.422-.14-1.027-.168-1.772z"/>
9
- </g>
10
- </svg>
11
- );
4
+ return (
5
+ <svg {...props} viewBox="0 -6 32 32" xmlns="http://www.w3.org/2000/svg">
6
+ <g fill="none" fillRule="evenodd">
7
+ <path d="M0-6h32v32H0z" />
8
+ <path
9
+ fill="#c00"
10
+ fillRule="nonzero"
11
+ d="M.985 19.636s.422-4.163 3.375-9.087c2.954-4.924 7.99-8.65 12.083-9.017 8.144-.816 15.46 6.485 15.46 6.485s-.24.168-.494.38C23.42 2.49 18.54 5.274 17.005 6.02c-7.033 3.925-4.91 13.616-4.91 13.616H.987zM24.137 2.32c-.45-.182-.9-.35-1.364-.505l.056-.93c.885.254 1.237.423 1.363.493l-.056.943zM22.8 5.304c.45.028.915.084 1.393.183l-.056.872c-.464-.1-.928-.155-1.392-.17l.056-.885zM17.597.913c-.407 0-.815.015-1.223.058l-.268-.83c.465-.056.915-.084 1.35-.084l.282.858h-.14zm.676 5.178c.35-.154.76-.31 1.237-.45l.31.93c-.41.125-.817.294-1.225.49l-.323-.97zm-6.386-3.7c-.366.184-.718.395-1.083.62l-.647-.985c.38-.225.745-.42 1.097-.604l.633.97zm2.883 6.33c.252-.323.548-.646.87-.942l.634.957c-.31.323-.59.647-.83 1L14.77 8.72zm-2.04 4.53c.112-.506.24-1.027.422-1.547l1.012.802c-.14.548-.24 1.097-.295 1.645l-1.14-.9zM6.57 6.57c-.34.35-.662.73-.958 1.11L4.53 6.752c.323-.352.674-.704 1.04-1.055l1 .872zm-4.25 6.286c-.224.52-.52 1.21-.702 1.688L0 13.954c.14-.38.436-1.084.703-1.69l1.618.592zm10.2 3.967l1.518.548c.084.663.21 1.28.337 1.83l-1.688-.605c-.07-.422-.14-1.027-.168-1.772z"
12
+ />
13
+ </g>
14
+ </svg>
15
+ )
12
16
  }
@@ -1,14 +1,17 @@
1
- import { InputHTMLAttributes } from 'react';
1
+ import { InputHTMLAttributes } from 'react'
2
2
 
3
- export default function Checkbox({ className = '', ...props }: InputHTMLAttributes<HTMLInputElement>) {
4
- return (
5
- <input
6
- {...props}
7
- type="checkbox"
8
- className={
9
- 'rounded dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800 ' +
10
- className
11
- }
12
- />
13
- );
3
+ export default function Checkbox({
4
+ className = '',
5
+ ...props
6
+ }: InputHTMLAttributes<HTMLInputElement>) {
7
+ return (
8
+ <input
9
+ {...props}
10
+ type="checkbox"
11
+ className={
12
+ 'rounded dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800 ' +
13
+ className
14
+ }
15
+ />
16
+ )
14
17
  }
@@ -1,17 +1,22 @@
1
- import { ButtonHTMLAttributes } from 'react';
1
+ import { ButtonHTMLAttributes } from 'react'
2
2
 
3
- export default function DangerButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
4
- return (
5
- <button
6
- {...props}
7
- className={
8
- `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 ${
9
- disabled && 'opacity-25'
10
- } ` + className
11
- }
12
- disabled={disabled}
13
- >
14
- {children}
15
- </button>
16
- );
3
+ export default function DangerButton({
4
+ className = '',
5
+ disabled,
6
+ children,
7
+ ...props
8
+ }: ButtonHTMLAttributes<HTMLButtonElement>) {
9
+ return (
10
+ <button
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
+ }
17
+ disabled={disabled}
18
+ >
19
+ {children}
20
+ </button>
21
+ )
17
22
  }
@@ -1,99 +1,131 @@
1
- import { useState, createContext, useContext, Fragment, PropsWithChildren, Dispatch, SetStateAction } from 'react';
2
- import { Link, InertiaLinkProps } from '@inertiajs/react';
3
- import { Transition } from '@headlessui/react';
1
+ import {
2
+ useState,
3
+ createContext,
4
+ useContext,
5
+ Fragment,
6
+ PropsWithChildren,
7
+ Dispatch,
8
+ SetStateAction,
9
+ } from 'react'
10
+ import { Link, InertiaLinkProps } from '@inertiajs/react'
11
+ import { Transition } from '@headlessui/react'
4
12
 
5
13
  const DropDownContext = createContext<{
6
- open: boolean;
7
- setOpen: Dispatch<SetStateAction<boolean>>;
8
- toggleOpen: () => void;
14
+ open: boolean
15
+ setOpen: Dispatch<SetStateAction<boolean>>
16
+ toggleOpen: () => void
9
17
  }>({
10
- open: false,
11
- setOpen: () => {},
12
- toggleOpen: () => {},
13
- });
18
+ open: false,
19
+ setOpen: () => {},
20
+ toggleOpen: () => {},
21
+ })
14
22
 
15
23
  const Dropdown = ({ children }: PropsWithChildren) => {
16
- const [open, setOpen] = useState(false);
24
+ const [open, setOpen] = useState(false)
17
25
 
18
- const toggleOpen = () => {
19
- setOpen((previousState) => !previousState);
20
- };
26
+ const toggleOpen = () => {
27
+ setOpen((previousState) => !previousState)
28
+ }
21
29
 
22
- return (
23
- <DropDownContext.Provider value={{ open, setOpen, toggleOpen }}>
24
- <div className="relative">{children}</div>
25
- </DropDownContext.Provider>
26
- );
27
- };
30
+ return (
31
+ <DropDownContext.Provider value={{ open, setOpen, toggleOpen }}>
32
+ <div className="relative">{children}</div>
33
+ </DropDownContext.Provider>
34
+ )
35
+ }
28
36
 
29
37
  const Trigger = ({ children }: PropsWithChildren) => {
30
- const { open, setOpen, toggleOpen } = useContext(DropDownContext);
31
-
32
- return (
33
- <>
34
- <div onClick={toggleOpen}>{children}</div>
35
-
36
- {open && <div className="fixed inset-0 z-40" onClick={() => setOpen(false)}></div>}
37
- </>
38
- );
39
- };
40
-
41
- const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white dark:bg-gray-700', children }: PropsWithChildren<{ align?: 'left'|'right', width?: '48', contentClasses?: string }>) => {
42
- const { open, setOpen } = useContext(DropDownContext);
43
-
44
- let alignmentClasses = 'origin-top';
45
-
46
- if (align === 'left') {
47
- alignmentClasses = 'ltr:origin-top-left rtl:origin-top-right start-0';
48
- } else if (align === 'right') {
49
- alignmentClasses = 'ltr:origin-top-right rtl:origin-top-left end-0';
50
- }
51
-
52
- let widthClasses = '';
53
-
54
- if (width === '48') {
55
- widthClasses = 'w-48';
56
- }
57
-
58
- return (
59
- <>
60
- <Transition
61
- as={Fragment}
62
- show={open}
63
- enter="transition ease-out duration-200"
64
- enterFrom="opacity-0 scale-95"
65
- enterTo="opacity-100 scale-100"
66
- leave="transition ease-in duration-75"
67
- leaveFrom="opacity-100 scale-100"
68
- leaveTo="opacity-0 scale-95"
69
- >
70
- <div
71
- className={`absolute z-50 mt-2 rounded-md shadow-lg ${alignmentClasses} ${widthClasses}`}
72
- onClick={() => setOpen(false)}
73
- >
74
- <div className={`rounded-md ring-1 ring-black ring-opacity-5 ` + contentClasses}>{children}</div>
75
- </div>
76
- </Transition>
77
- </>
78
- );
79
- };
80
-
81
- const DropdownLink = ({ className = '', children, ...props }: InertiaLinkProps) => {
82
- return (
83
- <Link
84
- {...props}
38
+ const { open, setOpen, toggleOpen } = useContext(DropDownContext)
39
+
40
+ return (
41
+ <>
42
+ <div onClick={toggleOpen}>{children}</div>
43
+
44
+ {open && (
45
+ <div
46
+ className="fixed inset-0 z-40"
47
+ onClick={() => setOpen(false)}
48
+ ></div>
49
+ )}
50
+ </>
51
+ )
52
+ }
53
+
54
+ const Content = ({
55
+ align = 'right',
56
+ width = '48',
57
+ contentClasses = 'py-1 bg-white dark:bg-gray-700',
58
+ children,
59
+ }: PropsWithChildren<{
60
+ align?: 'left' | 'right'
61
+ width?: '48'
62
+ contentClasses?: string
63
+ }>) => {
64
+ const { open, setOpen } = useContext(DropDownContext)
65
+
66
+ let alignmentClasses = 'origin-top'
67
+
68
+ if (align === 'left') {
69
+ alignmentClasses = 'ltr:origin-top-left rtl:origin-top-right start-0'
70
+ } else if (align === 'right') {
71
+ alignmentClasses = 'ltr:origin-top-right rtl:origin-top-left end-0'
72
+ }
73
+
74
+ let widthClasses = ''
75
+
76
+ if (width === '48') {
77
+ widthClasses = 'w-48'
78
+ }
79
+
80
+ return (
81
+ <>
82
+ <Transition
83
+ as={Fragment}
84
+ show={open}
85
+ enter="transition ease-out duration-200"
86
+ enterFrom="opacity-0 scale-95"
87
+ enterTo="opacity-100 scale-100"
88
+ leave="transition ease-in duration-75"
89
+ leaveFrom="opacity-100 scale-100"
90
+ leaveTo="opacity-0 scale-95"
91
+ >
92
+ <div
93
+ className={`absolute z-50 mt-2 rounded-md shadow-lg ${alignmentClasses} ${widthClasses}`}
94
+ onClick={() => setOpen(false)}
95
+ >
96
+ <div
85
97
  className={
86
- 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out ' +
87
- className
98
+ `rounded-md ring-1 ring-black ring-opacity-5 ` + contentClasses
88
99
  }
89
- >
100
+ >
90
101
  {children}
91
- </Link>
92
- );
93
- };
94
-
95
- Dropdown.Trigger = Trigger;
96
- Dropdown.Content = Content;
97
- Dropdown.Link = DropdownLink;
98
-
99
- export default Dropdown;
102
+ </div>
103
+ </div>
104
+ </Transition>
105
+ </>
106
+ )
107
+ }
108
+
109
+ const DropdownLink = ({
110
+ className = '',
111
+ children,
112
+ ...props
113
+ }: InertiaLinkProps) => {
114
+ return (
115
+ <Link
116
+ {...props}
117
+ className={
118
+ 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out ' +
119
+ className
120
+ }
121
+ >
122
+ {children}
123
+ </Link>
124
+ )
125
+ }
126
+
127
+ Dropdown.Trigger = Trigger
128
+ Dropdown.Content = Content
129
+ Dropdown.Link = DropdownLink
130
+
131
+ export default Dropdown
@@ -1,9 +1,16 @@
1
- import { HTMLAttributes } from 'react';
1
+ import { HTMLAttributes } from 'react'
2
2
 
3
- export default function InputError({ message, className = '', ...props }: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
4
- return message ? (
5
- <p {...props} className={'text-sm text-red-600 dark:text-red-400 ' + className}>
6
- {message}
7
- </p>
8
- ) : null;
3
+ export default function InputError({
4
+ message,
5
+ className = '',
6
+ ...props
7
+ }: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
8
+ return message ? (
9
+ <p
10
+ {...props}
11
+ className={'text-sm text-red-600 dark:text-red-400 ' + className}
12
+ >
13
+ {message}
14
+ </p>
15
+ ) : null
9
16
  }