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.
- checksums.yaml +4 -4
- data/lib/kaze/commands/install_command.rb +25 -13
- data/lib/kaze/commands/installs_hotwire_stack.rb +11 -8
- data/lib/kaze/commands/installs_inertia_stacks.rb +34 -24
- data/lib/kaze/commands/version_command.rb +6 -0
- data/lib/kaze/version.rb +1 -1
- data/lib/kaze.rb +1 -3
- data/stubs/default/app/forms/auth/login_form.rb +2 -8
- data/stubs/default/app/forms/auth/new_password_form.rb +1 -1
- data/stubs/default/app/forms/update_profile_information_form.rb +1 -1
- data/stubs/default/app/mailers/application_mailer.rb +4 -4
- data/stubs/default/app/mailers/user_mailer.rb +1 -1
- data/stubs/default/app/models/auth.rb +57 -0
- data/stubs/default/app/models/current.rb +1 -1
- data/stubs/default/app/validators/current_password_validator.rb +1 -1
- data/stubs/default/app/validators/email_validator.rb +1 -1
- data/stubs/default/app/validators/lowercase_validator.rb +2 -2
- data/stubs/default/app/views/layouts/mailer.html.erb +367 -372
- data/stubs/default/app/views/layouts/mailer.text.erb +1 -4
- data/stubs/default/app/views/user_mailer/reset_password.html.erb +21 -26
- data/stubs/default/config/routes.rb +16 -16
- data/stubs/default/db/migrate/20240101000001_create_delayed_jobs.rb +1 -1
- data/stubs/default/test/factories/users.rb +7 -0
- data/stubs/default/test/integration/auth/authentication_test.rb +43 -0
- data/stubs/default/test/integration/auth/password_reset_test.rb +41 -0
- data/stubs/default/test/integration/auth/registration_test.rb +21 -0
- data/stubs/default/test/integration/password_update_test.rb +28 -0
- data/stubs/default/test/integration/profile_test.rb +51 -0
- data/stubs/default/test/test_helper.rb +38 -0
- data/stubs/hotwire/app/components/danger_button_component.rb +1 -1
- data/stubs/hotwire/app/components/dropdown_component.html.erb +17 -18
- data/stubs/hotwire/app/components/dropdown_component.rb +7 -7
- data/stubs/hotwire/app/components/modal_component.html.erb +55 -59
- data/stubs/hotwire/app/components/modal_component.rb +6 -6
- data/stubs/hotwire/app/components/primary_button_component.rb +1 -1
- data/stubs/hotwire/app/components/secondary_button_component.rb +1 -1
- data/stubs/hotwire/app/controllers/application_controller.rb +1 -0
- data/stubs/hotwire/app/controllers/auth/authenticated_session_controller.rb +12 -9
- data/stubs/hotwire/app/controllers/auth/new_password_controller.rb +7 -5
- data/stubs/hotwire/app/controllers/auth/password_reset_link_controller.rb +7 -5
- data/stubs/hotwire/app/controllers/auth/registered_user_controller.rb +7 -5
- data/stubs/hotwire/app/controllers/concerns/authenticate.rb +5 -20
- data/stubs/hotwire/app/controllers/concerns/redirect_if_authenticated.rb +19 -0
- data/stubs/hotwire/app/controllers/concerns/set_current_auth.rb +9 -0
- data/stubs/hotwire/app/controllers/password_controller.rb +3 -3
- data/stubs/hotwire/app/controllers/profile_controller.rb +11 -9
- data/stubs/hotwire/app/controllers/welcome_controller.rb +1 -1
- data/stubs/hotwire/app/javascript/application.js +3 -3
- data/stubs/hotwire/app/views/auth/forgot_password.html.erb +12 -17
- data/stubs/hotwire/app/views/auth/login.html.erb +0 -9
- data/stubs/hotwire/app/views/auth/register.html.erb +0 -13
- data/stubs/hotwire/app/views/auth/reset_password.html.erb +0 -7
- data/stubs/hotwire/app/views/dashboard/index.html.erb +9 -10
- data/stubs/hotwire/app/views/layouts/_navigation.html.erb +77 -87
- data/stubs/hotwire/app/views/layouts/application.html.erb +0 -9
- data/stubs/hotwire/app/views/layouts/guest.html.erb +0 -6
- data/stubs/hotwire/app/views/profile/edit.html.erb +19 -22
- data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +32 -42
- data/stubs/hotwire/app/views/profile/partials/_update_password_form.html.erb +42 -55
- data/stubs/hotwire/app/views/profile/partials/_update_profile_information_form.html.erb +36 -46
- data/stubs/hotwire/app/views/welcome/index.html.erb +34 -46
- data/stubs/hotwire/config/importmap.rb +3 -3
- data/stubs/hotwire/config/tailwind.config.js +2 -2
- data/stubs/inertia-common/app/controllers/application_controller.rb +1 -0
- data/stubs/inertia-common/app/controllers/auth/authenticated_session_controller.rb +11 -8
- data/stubs/inertia-common/app/controllers/auth/new_password_controller.rb +5 -3
- data/stubs/inertia-common/app/controllers/auth/password_reset_link_controller.rb +5 -3
- data/stubs/inertia-common/app/controllers/auth/registered_user_controller.rb +5 -3
- data/stubs/inertia-common/app/controllers/concerns/authenticate.rb +5 -20
- data/stubs/inertia-common/app/controllers/concerns/handle_inertia_requests.rb +1 -1
- data/stubs/inertia-common/app/controllers/concerns/redirect_if_authenticated.rb +19 -0
- data/stubs/inertia-common/app/controllers/concerns/set_current_auth.rb +9 -0
- data/stubs/inertia-common/app/controllers/concerns/verify_csrf_token.rb +4 -4
- data/stubs/inertia-common/app/controllers/dashboard_controller.rb +1 -1
- data/stubs/inertia-common/app/controllers/password_controller.rb +1 -1
- data/stubs/inertia-common/app/controllers/profile_controller.rb +7 -5
- data/stubs/inertia-common/app/controllers/welcome_controller.rb +2 -2
- data/stubs/inertia-common/bin/vite +6 -6
- data/stubs/inertia-common/test/integration/password_update_test.rb +28 -0
- data/stubs/inertia-common/test/integration/profile_test.rb +51 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ApplicationLogo.tsx +13 -9
- data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +15 -12
- data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +20 -15
- data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +119 -87
- data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +14 -7
- data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +18 -7
- data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +60 -60
- data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +21 -16
- data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +20 -15
- data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +19 -14
- data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +22 -16
- data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +35 -24
- data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +157 -117
- data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +15 -15
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +52 -49
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +90 -82
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +118 -115
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +63 -60
- data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +23 -17
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +31 -27
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +109 -99
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +121 -113
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +76 -69
- data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +87 -63
- data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +32 -25
- data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +0 -4
- data/stubs/inertia-react-ts/config/tailwind.config.js +2 -2
- data/stubs/inertia-react-ts/vite.config.ts +2 -5
- data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +10 -6
- data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +18 -18
- data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +5 -5
- data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +60 -57
- data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +9 -9
- data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +7 -7
- data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +6 -6
- data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +84 -74
- data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +12 -12
- data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +5 -5
- data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +12 -12
- data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +13 -13
- data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +13 -13
- data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +168 -136
- data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +15 -13
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +56 -49
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +78 -72
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +101 -97
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +71 -68
- data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +22 -14
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +34 -30
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +87 -83
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +105 -98
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +69 -59
- data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +74 -47
- data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +0 -4
- data/stubs/inertia-vue-ts/config/tailwind.config.js +2 -2
- data/stubs/inertia-vue-ts/vite.config.ts +2 -5
- metadata +19 -6
- data/stubs/hotwire/bin/vite +0 -27
- data/stubs/inertia-common/Procfile.dev +0 -3
- /data/stubs/{hotwire → default}/Procfile.dev +0 -0
- /data/stubs/hotwire/app/javascript/{alpinejs.js → alpinejs.stub} +0 -0
@@ -1,8 +1,10 @@
|
|
1
1
|
class Auth::RegisteredUserController < ApplicationController
|
2
|
-
|
2
|
+
include RedirectIfAuthenticated
|
3
|
+
|
4
|
+
skip_authenticate
|
3
5
|
|
4
6
|
def new
|
5
|
-
render inertia:
|
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 :
|
5
|
+
before_action :authenticate!
|
6
6
|
end
|
7
7
|
|
8
8
|
class_methods do
|
9
|
-
def
|
10
|
-
skip_before_action :
|
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
|
17
|
-
|
18
|
-
Current.user = user
|
19
|
-
else
|
20
|
-
redirect_to login_path
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def login(user)
|
25
|
-
Current.user = user
|
26
|
-
reset_session
|
27
|
-
session[:user_id] = user.id
|
28
|
-
end
|
29
|
-
|
30
|
-
def logout
|
31
|
-
Current.user = nil
|
32
|
-
reset_session
|
16
|
+
def authenticate!
|
17
|
+
redirect_to login_path unless Current.auth.check?
|
33
18
|
end
|
34
19
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RedirectIfAuthenticated
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :redirect_if_authenticated!
|
6
|
+
end
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def skip_redirect_if_authenticated(**options)
|
10
|
+
skip_before_action :redirect_if_authenticated!, **options
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def redirect_if_authenticated!
|
17
|
+
redirect_to dashboard_path if Current.auth.check?
|
18
|
+
end
|
19
|
+
end
|
@@ -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:
|
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[
|
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[
|
19
|
+
cookies['XSRF-TOKEN'] = {
|
20
20
|
value: form_authenticity_token,
|
21
|
-
same_site:
|
21
|
+
same_site: 'Strict'
|
22
22
|
}
|
23
23
|
end
|
24
24
|
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:
|
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
|
-
|
29
|
+
reset_session
|
30
|
+
|
31
|
+
redirect_to '/'
|
30
32
|
end
|
31
33
|
end
|
@@ -8,12 +8,12 @@
|
|
8
8
|
# this file is here to facilitate running it.
|
9
9
|
#
|
10
10
|
|
11
|
-
ENV[
|
11
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
12
12
|
|
13
|
-
bundle_binstub = File.expand_path(
|
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?(
|
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
|
25
|
-
require
|
24
|
+
require 'rubygems'
|
25
|
+
require 'bundler/setup'
|
26
26
|
|
27
|
-
load Gem.bin_path(
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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({
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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({
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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 {
|
2
|
-
|
3
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
14
|
+
open: boolean
|
15
|
+
setOpen: Dispatch<SetStateAction<boolean>>
|
16
|
+
toggleOpen: () => void
|
9
17
|
}>({
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
})
|
18
|
+
open: false,
|
19
|
+
setOpen: () => {},
|
20
|
+
toggleOpen: () => {},
|
21
|
+
})
|
14
22
|
|
15
23
|
const Dropdown = ({ children }: PropsWithChildren) => {
|
16
|
-
|
24
|
+
const [open, setOpen] = useState(false)
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
const toggleOpen = () => {
|
27
|
+
setOpen((previousState) => !previousState)
|
28
|
+
}
|
21
29
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
}
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
className
|
98
|
+
`rounded-md ring-1 ring-black ring-opacity-5 ` + contentClasses
|
88
99
|
}
|
89
|
-
|
100
|
+
>
|
90
101
|
{children}
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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({
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
}
|