kaze 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }