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