kaze 0.2.0 → 0.3.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 +22 -5
- data/lib/kaze/commands/installs_hotwire_stack.rb +62 -0
- data/lib/kaze/commands/{install_inertia_stacks.rb → installs_inertia_stacks.rb} +19 -17
- data/lib/kaze/version.rb +1 -1
- data/stubs/default/app/validators/lowercase_validator.rb +1 -0
- data/stubs/default/app/views/layouts/mailer.html.erb +3 -1
- data/stubs/default/app/views/layouts/mailer.text.erb +3 -1
- data/stubs/default/config/routes.rb +1 -1
- data/stubs/hotwire/Procfile.dev +2 -0
- data/stubs/hotwire/app/components/application_logo_component.rb +14 -0
- data/stubs/hotwire/app/components/auth_session_status_component.rb +17 -0
- data/stubs/hotwire/app/components/danger_button_component.rb +13 -0
- data/stubs/hotwire/app/components/dropdown_component.html.erb +20 -0
- data/stubs/hotwire/app/components/dropdown_component.rb +15 -0
- data/stubs/hotwire/app/components/dropdown_link_component.rb +12 -0
- data/stubs/hotwire/app/components/input_error_component.rb +18 -0
- data/stubs/hotwire/app/components/input_label_component.rb +13 -0
- data/stubs/hotwire/app/components/modal_component.html.erb +62 -0
- data/stubs/hotwire/app/components/modal_component.rb +14 -0
- data/stubs/hotwire/app/components/nav_link_component.rb +15 -0
- data/stubs/hotwire/app/components/primary_button_component.rb +13 -0
- data/stubs/hotwire/app/components/responsive_nav_link_component.rb +15 -0
- data/stubs/hotwire/app/components/secondary_button_component.rb +13 -0
- data/stubs/hotwire/app/components/text_input_component.rb +11 -0
- data/stubs/hotwire/app/controllers/application_controller.rb +3 -0
- data/stubs/hotwire/app/controllers/auth/authenticated_session_controller.rb +29 -0
- data/stubs/hotwire/app/controllers/auth/new_password_controller.rb +19 -0
- data/stubs/hotwire/app/controllers/auth/password_reset_link_controller.rb +19 -0
- data/stubs/hotwire/app/controllers/auth/registered_user_controller.rb +23 -0
- data/stubs/hotwire/app/controllers/dashboard_controller.rb +4 -0
- data/stubs/hotwire/app/controllers/password_controller.rb +11 -0
- data/stubs/hotwire/app/controllers/profile_controller.rb +33 -0
- data/stubs/hotwire/app/controllers/welcome_controller.rb +7 -0
- data/stubs/hotwire/app/javascript/alpinejs.js +2 -0
- data/stubs/hotwire/app/javascript/application.js +8 -0
- data/stubs/hotwire/app/views/auth/forgot_password.html.erb +23 -0
- data/stubs/hotwire/app/views/auth/login.html.erb +40 -0
- data/stubs/hotwire/app/views/auth/register.html.erb +47 -0
- data/stubs/hotwire/app/views/auth/reset_password.html.erb +28 -0
- data/stubs/hotwire/app/views/dashboard/index.html.erb +15 -0
- data/stubs/hotwire/app/views/layouts/_navigation.html.erb +92 -0
- data/stubs/hotwire/app/views/layouts/application.html.erb +42 -0
- data/stubs/hotwire/app/views/layouts/guest.html.erb +33 -0
- data/stubs/hotwire/app/views/profile/edit.html.erb +27 -0
- data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +48 -0
- data/stubs/hotwire/app/views/profile/partials/_update_password_form.html.erb +58 -0
- data/stubs/hotwire/app/views/profile/partials/_update_profile_information_form.html.erb +49 -0
- data/stubs/hotwire/app/views/welcome/index.html.erb +68 -0
- data/stubs/hotwire/config/importmap.rb +3 -0
- data/stubs/hotwire/config/tailwind.config.js +23 -0
- data/stubs/inertia-common/app/controllers/concerns/authenticate.rb +34 -0
- data/stubs/{default → inertia-common}/app/controllers/dashboard_controller.rb +1 -1
- data/stubs/inertia-common/bin/vite +27 -0
- data/stubs/inertia-common/config/vite.json +16 -0
- data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +0 -4
- data/stubs/inertia-react-ts/config/tailwind.config.js +1 -1
- data/stubs/inertia-react-ts/package.json +24 -24
- data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +1 -1
- data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +0 -4
- data/stubs/inertia-vue-ts/config/tailwind.config.js +1 -1
- metadata +63 -18
- data/stubs/default/config/vite.json +0 -16
- /data/stubs/{default → hotwire}/app/controllers/concerns/authenticate.rb +0 -0
- /data/stubs/{default → hotwire}/bin/vite +0 -0
- /data/stubs/{default → inertia-common}/Procfile.dev +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/application_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/auth/authenticated_session_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/auth/new_password_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/auth/password_reset_link_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/auth/registered_user_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/concerns/handle_inertia_requests.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/concerns/verify_csrf_token.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/password_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/profile_controller.rb +0 -0
- /data/stubs/{default → inertia-common}/app/controllers/welcome_controller.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 367b549562d6d96be85bac57b8d0f4528598122dfe773d733fcd992de4e1616d
|
4
|
+
data.tar.gz: 9d79187ce1918ff96854c36cb5e2f51e05aaf6a329afeede53a35151eaeb433d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f87b1598f94dbdead93d4dea21d766d57819ec6bf354093e45af74c5659ebf7c31927a09f22138e5bd4fd122a1210a3aef92dbd5e0f2f492978bd44000e2d2db
|
7
|
+
data.tar.gz: ff833ef663bc961c1ab98c8ab671a26b04ae4da50c57a491a9a96c713b97068570f843905f6ef03591bdf53f3c306f18d881e6b75b56b0039423b5547fd84ea6
|
@@ -4,10 +4,15 @@ require "open3"
|
|
4
4
|
require "thor"
|
5
5
|
|
6
6
|
class Kaze::Commands::InstallCommand < Thor
|
7
|
-
include Kaze::Commands::
|
7
|
+
include Kaze::Commands::InstallsHotwireStack
|
8
|
+
include Kaze::Commands::InstallsInertiaStacks
|
8
9
|
|
9
10
|
desc "install [STACK]", "Install the Kaze controllers and resources. Supported stacks: react, vue."
|
10
11
|
def install(stack = "hotwire")
|
12
|
+
if stack == "hotwire"
|
13
|
+
return install_hotwire_stack
|
14
|
+
end
|
15
|
+
|
11
16
|
if stack == "react"
|
12
17
|
return install_inertia_react_stack
|
13
18
|
end
|
@@ -21,14 +26,26 @@ class Kaze::Commands::InstallCommand < Thor
|
|
21
26
|
|
22
27
|
private
|
23
28
|
|
24
|
-
def
|
29
|
+
def install_gems(gems = [], group = nil)
|
30
|
+
installed_gems = Bundler::Definition.build("#{Dir.pwd}/Gemfile", nil, {}).dependencies.map(&:name)
|
31
|
+
|
32
|
+
gem_being_installed = gems.map { |gem| gem unless installed_gems.include?(gem) }.compact
|
33
|
+
|
34
|
+
return true if gem_being_installed.empty?
|
35
|
+
|
36
|
+
status = run_command("bundle add #{gem_being_installed.join(" ")}#{group ? " --group \"#{group}\"" : ""}")
|
37
|
+
|
38
|
+
status.success?
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove_gems(gems = [])
|
25
42
|
installed_gems = Bundler::Definition.build("#{Dir.pwd}/Gemfile", nil, {}).dependencies.map(&:name)
|
26
43
|
|
27
|
-
|
44
|
+
gems_being_removed = gems.map { |gem| gem if installed_gems.include?(gem) }.compact
|
28
45
|
|
29
|
-
return true if
|
46
|
+
return true if gems_being_removed.empty?
|
30
47
|
|
31
|
-
status = run_command("bundle
|
48
|
+
status = run_command("bundle remove #{gems_being_removed.join(" ")}")
|
32
49
|
|
33
50
|
status.success?
|
34
51
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Kaze::Commands::InstallsHotwireStack
|
2
|
+
private
|
3
|
+
|
4
|
+
def install_hotwire_stack
|
5
|
+
# Gems...
|
6
|
+
return unless remove_gems([ "sprockets-rails" ])
|
7
|
+
return unless install_gems([ "propshaft", "view_component", "tailwindcss-rails", "turbo-rails", "dotenv", "bcrypt" ])
|
8
|
+
return unless install_gems([ "hotwire-livereload" ], "development")
|
9
|
+
|
10
|
+
# Controllers...
|
11
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/hotwire/app/controllers", "#{Dir.pwd}/app/controllers")
|
12
|
+
|
13
|
+
# Models...
|
14
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/models", "#{Dir.pwd}/app/models")
|
15
|
+
|
16
|
+
# Forms...
|
17
|
+
ensure_directory_exists("#{Dir.pwd}/app/forms")
|
18
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/forms", "#{Dir.pwd}/app/forms")
|
19
|
+
|
20
|
+
# Validators...
|
21
|
+
ensure_directory_exists("#{Dir.pwd}/app/validators")
|
22
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/validators", "#{Dir.pwd}/app/validators")
|
23
|
+
|
24
|
+
# Mailers...
|
25
|
+
ensure_directory_exists("#{Dir.pwd}/app/mailers")
|
26
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/mailers", "#{Dir.pwd}/app/mailers")
|
27
|
+
|
28
|
+
# Views...
|
29
|
+
ensure_directory_exists("#{Dir.pwd}/app/views/layouts")
|
30
|
+
ensure_directory_exists("#{Dir.pwd}/app/views/user_mailer")
|
31
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/hotwire/app/views", "#{Dir.pwd}/app/views")
|
32
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.html.erb", "#{Dir.pwd}/app/views/layouts/mailer.html.erb")
|
33
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.text.erb", "#{Dir.pwd}/app/views/layouts/mailer.text.erb")
|
34
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/user_mailer/reset_password.html.erb", "#{Dir.pwd}/app/views/user_mailer/reset_password.html.erb")
|
35
|
+
|
36
|
+
# Components + Pages...
|
37
|
+
ensure_directory_exists("#{Dir.pwd}/app/components")
|
38
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/hotwire/app/components", "#{Dir.pwd}/app/components")
|
39
|
+
ensure_directory_exists("#{Dir.pwd}/app/javascript")
|
40
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/hotwire/app/javascript", "#{Dir.pwd}/app/javascript")
|
41
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/hotwire/config/importmap.rb", "#{Dir.pwd}/config/importmap.rb")
|
42
|
+
|
43
|
+
# Tests...
|
44
|
+
|
45
|
+
# Routes...
|
46
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/config/routes.rb", "#{Dir.pwd}/config/routes.rb")
|
47
|
+
|
48
|
+
# Migrations...
|
49
|
+
install_migrations
|
50
|
+
|
51
|
+
# Tailwind...
|
52
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/assets/stylesheets/application.css", "#{Dir.pwd}/app/assets/stylesheets/application.css")
|
53
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/assets/stylesheets/application.tailwind.css", "#{Dir.pwd}/app/assets/stylesheets/application.tailwind.css")
|
54
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/hotwire/config/tailwind.config.js", "#{Dir.pwd}/config/tailwind.config.js")
|
55
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/bin/dev", "#{Dir.pwd}/bin/dev")
|
56
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/hotwire/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
|
57
|
+
run_command("rails tailwindcss:build")
|
58
|
+
|
59
|
+
say ""
|
60
|
+
say "Kaze scaffolding installed successfully.", :green
|
61
|
+
end
|
62
|
+
end
|
@@ -1,15 +1,16 @@
|
|
1
|
-
module Kaze::Commands::
|
1
|
+
module Kaze::Commands::InstallsInertiaStacks
|
2
2
|
private
|
3
3
|
|
4
4
|
def install_inertia_react_stack
|
5
|
-
#
|
6
|
-
return unless
|
5
|
+
# Gems...
|
6
|
+
return unless remove_gems([ "sprockets-rails" ])
|
7
|
+
return unless install_gems([ "propshaft", "tailwindcss-rails", "inertia_rails", "vite_rails", "dotenv", "bcrypt", "js-routes" ])
|
7
8
|
|
8
9
|
# NPM Packages...
|
9
10
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-react-ts/package.json", "#{Dir.pwd}/package.json")
|
10
11
|
|
11
12
|
# Controllers...
|
12
|
-
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/
|
13
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/app/controllers", "#{Dir.pwd}/app/controllers")
|
13
14
|
|
14
15
|
# Models...
|
15
16
|
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/models", "#{Dir.pwd}/app/models")
|
@@ -22,6 +23,10 @@ module Kaze::Commands::InstallInertiaStacks
|
|
22
23
|
ensure_directory_exists("#{Dir.pwd}/app/validators")
|
23
24
|
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/validators", "#{Dir.pwd}/app/validators")
|
24
25
|
|
26
|
+
# Mailers...
|
27
|
+
ensure_directory_exists("#{Dir.pwd}/app/mailers")
|
28
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/mailers", "#{Dir.pwd}/app/mailers")
|
29
|
+
|
25
30
|
# Views...
|
26
31
|
ensure_directory_exists("#{Dir.pwd}/app/views/layouts")
|
27
32
|
ensure_directory_exists("#{Dir.pwd}/app/views/user_mailer")
|
@@ -30,10 +35,6 @@ module Kaze::Commands::InstallInertiaStacks
|
|
30
35
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.text.erb", "#{Dir.pwd}/app/views/layouts/mailer.text.erb")
|
31
36
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/user_mailer/reset_password.html.erb", "#{Dir.pwd}/app/views/user_mailer/reset_password.html.erb")
|
32
37
|
|
33
|
-
# Mailers...
|
34
|
-
ensure_directory_exists("#{Dir.pwd}/app/mailers")
|
35
|
-
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/mailers", "#{Dir.pwd}/app/mailers")
|
36
|
-
|
37
38
|
# Components + Pages...
|
38
39
|
ensure_directory_exists("#{Dir.pwd}/app/javascript")
|
39
40
|
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/inertia-react-ts/app/javascript", "#{Dir.pwd}/app/javascript")
|
@@ -50,12 +51,12 @@ module Kaze::Commands::InstallInertiaStacks
|
|
50
51
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/assets/stylesheets/application.css", "#{Dir.pwd}/app/assets/stylesheets/application.css")
|
51
52
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/assets/stylesheets/application.tailwind.css", "#{Dir.pwd}/app/assets/stylesheets/application.tailwind.css")
|
52
53
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-react-ts/config/tailwind.config.js", "#{Dir.pwd}/config/tailwind.config.js")
|
53
|
-
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/
|
54
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/config/vite.json", "#{Dir.pwd}/config/vite.json")
|
54
55
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-react-ts/tsconfig.json", "#{Dir.pwd}/tsconfig.json")
|
55
56
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-react-ts/vite.config.ts", "#{Dir.pwd}/vite.config.ts")
|
56
57
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/bin/dev", "#{Dir.pwd}/bin/dev")
|
57
|
-
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/
|
58
|
-
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/
|
58
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/bin/vite", "#{Dir.pwd}/bin/vite")
|
59
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
|
59
60
|
run_command("rails generate js_routes:middleware")
|
60
61
|
|
61
62
|
say ""
|
@@ -76,14 +77,15 @@ module Kaze::Commands::InstallInertiaStacks
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def install_inertia_vue_stack
|
79
|
-
#
|
80
|
-
return unless
|
80
|
+
# Gems...
|
81
|
+
return unless remove_gems([ "sprockets-rails" ])
|
82
|
+
return unless install_gems([ "propshaft", "tailwindcss-rails", "inertia_rails", "vite_rails", "dotenv", "bcrypt", "js-routes" ])
|
81
83
|
|
82
84
|
# NPM Packages...
|
83
85
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-vue-ts/package.json", "#{Dir.pwd}/package.json")
|
84
86
|
|
85
87
|
# Controllers...
|
86
|
-
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/
|
88
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/app/controllers", "#{Dir.pwd}/app/controllers")
|
87
89
|
|
88
90
|
# Models...
|
89
91
|
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/models", "#{Dir.pwd}/app/models")
|
@@ -124,12 +126,12 @@ module Kaze::Commands::InstallInertiaStacks
|
|
124
126
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/assets/stylesheets/application.css", "#{Dir.pwd}/app/assets/stylesheets/application.css")
|
125
127
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/assets/stylesheets/application.tailwind.css", "#{Dir.pwd}/app/assets/stylesheets/application.tailwind.css")
|
126
128
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-vue-ts/config/tailwind.config.js", "#{Dir.pwd}/config/tailwind.config.js")
|
127
|
-
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/
|
129
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/config/vite.json", "#{Dir.pwd}/config/vite.json")
|
128
130
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-vue-ts/tsconfig.json", "#{Dir.pwd}/tsconfig.json")
|
129
131
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-vue-ts/vite.config.ts", "#{Dir.pwd}/vite.config.ts")
|
130
132
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/bin/dev", "#{Dir.pwd}/bin/dev")
|
131
|
-
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/
|
132
|
-
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/
|
133
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/bin/vite", "#{Dir.pwd}/bin/vite")
|
134
|
+
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
|
133
135
|
run_command("rails generate js_routes:middleware")
|
134
136
|
|
135
137
|
say ""
|
data/lib/kaze/version.rb
CHANGED
@@ -340,13 +340,15 @@ img {
|
|
340
340
|
<%= yield %>
|
341
341
|
|
342
342
|
<!-- Subcopy -->
|
343
|
+
<% if content_for?(:subcopy) %>
|
343
344
|
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
344
345
|
<tr>
|
345
346
|
<td>
|
346
|
-
<%= yield
|
347
|
+
<%= yield :subcopy %>
|
347
348
|
</td>
|
348
349
|
</tr>
|
349
350
|
</table>
|
351
|
+
<% end %>
|
350
352
|
|
351
353
|
</td>
|
352
354
|
</tr>
|
@@ -17,7 +17,7 @@ Rails.application.routes.draw do
|
|
17
17
|
|
18
18
|
post "logout", to: "auth/authenticated_session#destroy", as: :logout
|
19
19
|
|
20
|
-
get "dashboard", to: "dashboard#
|
20
|
+
get "dashboard", to: "dashboard#index", as: :dashboard
|
21
21
|
|
22
22
|
get "profile", to: "profile#edit", as: :profile_edit
|
23
23
|
patch "profile", to: "profile#update", as: :profile_update
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ApplicationLogoComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<svg viewBox="0 -6 32 32" xmlns="http://www.w3.org/2000/svg" <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<g fill="none" fill-rule="evenodd">
|
5
|
+
<path d="M0-6h32v32H0z"/>
|
6
|
+
<path fill="#c00" fill-rule="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"/>
|
7
|
+
</g>
|
8
|
+
</svg>
|
9
|
+
ERB
|
10
|
+
|
11
|
+
def initialize(attributes = {})
|
12
|
+
@attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class AuthSessionStatusComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<div <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= @status %>
|
5
|
+
</div>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@status = attributes[:status]
|
10
|
+
attributes[:class] = "font-medium text-sm text-green-600 dark:text-green-400#{" #{attributes[:class]}" if attributes[:class]}"
|
11
|
+
@attributes = attributes.without(:status).map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
12
|
+
end
|
13
|
+
|
14
|
+
def render?
|
15
|
+
@status.present?
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class DangerButtonComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<button <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= content %>
|
5
|
+
</button>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
attributes[:type] = attributes[:type] || "submit"
|
10
|
+
attributes[:class] = "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#{" #{attributes[:class]}" if attributes[:class]}"
|
11
|
+
@attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
2
|
+
<div @click="open = ! open">
|
3
|
+
<%= trigger %>
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<div x-show="open"
|
7
|
+
x-transition:enter="transition ease-out duration-200"
|
8
|
+
x-transition:enter-start="opacity-0 scale-95"
|
9
|
+
x-transition:enter-end="opacity-100 scale-100"
|
10
|
+
x-transition:leave="transition ease-in duration-75"
|
11
|
+
x-transition:leave-start="opacity-100 scale-100"
|
12
|
+
x-transition:leave-end="opacity-0 scale-95"
|
13
|
+
class="absolute z-50 mt-2 <%= @width %> rounded-md shadow-lg <%= @alignment_classes %>"
|
14
|
+
style="display: none;"
|
15
|
+
@click="open = false">
|
16
|
+
<div class="rounded-md ring-1 ring-black ring-opacity-5 <%= @content_classes %>">
|
17
|
+
<%= content %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class DropdownComponent < ViewComponent::Base
|
2
|
+
renders_one :trigger
|
3
|
+
|
4
|
+
def initialize(align: "right", width: "48", content_classes: "py-1 bg-white dark:bg-gray-700")
|
5
|
+
if align == "left"
|
6
|
+
@alignment_classes = "ltr:origin-top-left rtl:origin-top-right start-0"
|
7
|
+
elsif align == "top"
|
8
|
+
@alignment_classes = "origin-top"
|
9
|
+
else
|
10
|
+
@alignment_classes = "ltr:origin-top-right rtl:origin-top-left end-0"
|
11
|
+
end
|
12
|
+
@width = width == "48" ? "w-48" : width
|
13
|
+
@content_classes = content_classes
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class DropdownLinkComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<a <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= content %>
|
5
|
+
</a>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
attributes[:class] = "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#{" #{attributes[:class]}" if attributes[:class]}"
|
10
|
+
@attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class InputErrorComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<div <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<p class="text-sm text-red-600 dark:text-red-400">
|
5
|
+
<%= @message %>
|
6
|
+
</p>
|
7
|
+
</div>
|
8
|
+
ERB
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
@message = attributes[:message]
|
12
|
+
@attributes = attributes.without(:message).map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
13
|
+
end
|
14
|
+
|
15
|
+
def render?
|
16
|
+
@message.present?
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class InputLabelComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<a <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= @value ? @value : content %>
|
5
|
+
</a>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@value = attributes[:value] || nil
|
10
|
+
attributes[:class] = "block font-medium text-sm text-gray-700 dark:text-gray-300#{" #{attributes[:class]}" if attributes[:class]}"
|
11
|
+
@attributes = attributes.without(:value).map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<div
|
2
|
+
x-data="{
|
3
|
+
show: <%= @show %>,
|
4
|
+
focusables() {
|
5
|
+
// All focusable element types...
|
6
|
+
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
7
|
+
return [...$el.querySelectorAll(selector)]
|
8
|
+
// All non-disabled elements...
|
9
|
+
.filter(el => ! el.hasAttribute('disabled'))
|
10
|
+
},
|
11
|
+
firstFocusable() { return this.focusables()[0] },
|
12
|
+
lastFocusable() { return this.focusables().slice(-1)[0] },
|
13
|
+
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
14
|
+
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
15
|
+
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
16
|
+
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
17
|
+
}"
|
18
|
+
x-init="$watch('show', value => {
|
19
|
+
if (value) {
|
20
|
+
document.body.classList.add('overflow-y-hidden');
|
21
|
+
<%= @attributes.has_key?(:focusable) ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' %>
|
22
|
+
} else {
|
23
|
+
document.body.classList.remove('overflow-y-hidden');
|
24
|
+
}
|
25
|
+
})"
|
26
|
+
x-on:open-modal.window="$event.detail == '<%= @name %>' ? show = true : null"
|
27
|
+
x-on:close-modal.window="$event.detail == '<%= @name %>' ? show = false : null"
|
28
|
+
x-on:close.stop="show = false"
|
29
|
+
x-on:keydown.escape.window="show = false"
|
30
|
+
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
31
|
+
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
32
|
+
x-show="show"
|
33
|
+
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
34
|
+
style="display: <%= @show ? 'block' : 'none' %>;"
|
35
|
+
>
|
36
|
+
<div
|
37
|
+
x-show="show"
|
38
|
+
class="fixed inset-0 transform transition-all"
|
39
|
+
x-on:click="show = false"
|
40
|
+
x-transition:enter="ease-out duration-300"
|
41
|
+
x-transition:enter-start="opacity-0"
|
42
|
+
x-transition:enter-end="opacity-100"
|
43
|
+
x-transition:leave="ease-in duration-200"
|
44
|
+
x-transition:leave-start="opacity-100"
|
45
|
+
x-transition:leave-end="opacity-0"
|
46
|
+
>
|
47
|
+
<div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<div
|
51
|
+
x-show="show"
|
52
|
+
class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full <%= @max_width %> sm:mx-auto"
|
53
|
+
x-transition:enter="ease-out duration-300"
|
54
|
+
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
55
|
+
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
56
|
+
x-transition:leave="ease-in duration-200"
|
57
|
+
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
58
|
+
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
59
|
+
>
|
60
|
+
<%= content %>
|
61
|
+
</div>
|
62
|
+
</div>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ModalComponent < ViewComponent::Base
|
2
|
+
def initialize(attributes = {})
|
3
|
+
@name = attributes[:name]
|
4
|
+
@show = attributes[:show] || false
|
5
|
+
@max_width = {
|
6
|
+
:sm => 'sm:max-w-sm',
|
7
|
+
:md => 'sm:max-w-md',
|
8
|
+
:lg => 'sm:max-w-lg',
|
9
|
+
:xl => 'sm:max-w-xl',
|
10
|
+
'2xl' => 'sm:max-w-2xl',
|
11
|
+
}[attributes[:max_width] || '2xl']
|
12
|
+
@attributes = attributes.without(:name, :show, :max_width)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class NavLinkComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<a <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= content %>
|
5
|
+
</a>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
classes = attributes[:active] \
|
10
|
+
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' \
|
11
|
+
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out'
|
12
|
+
attributes[:class] = "#{classes}#{" #{attributes[:class]}" if attributes[:class]}"
|
13
|
+
@attributes = attributes.without(:active).map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class PrimaryButtonComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<button <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= content %>
|
5
|
+
</button>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
attributes[:type] = attributes[:type] || "submit"
|
10
|
+
attributes[:class] = "inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150#{" #{attributes[:class]}" if attributes[:class]}"
|
11
|
+
@attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ResponsiveNavLinkComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<a <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= content %>
|
5
|
+
</a>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
classes = attributes[:active] \
|
10
|
+
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out' \
|
11
|
+
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out'
|
12
|
+
attributes[:class] = "#{classes}#{" #{attributes[:class]}" if attributes[:class]}"
|
13
|
+
@attributes = attributes.without(:active).map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class SecondaryButtonComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<button <%= sanitize @attributes.join(" ") %>>
|
4
|
+
<%= content %>
|
5
|
+
</button>
|
6
|
+
ERB
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
attributes[:type] = attributes[:type] || "button"
|
10
|
+
attributes[:class] = "inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150#{" #{attributes[:class]}" if attributes[:class]}"
|
11
|
+
@attributes = attributes.map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class TextInputComponent < ViewComponent::Base
|
2
|
+
erb_template <<~ERB
|
3
|
+
<input <%= @disabled ? "disabled" : "" %> <%= sanitize @attributes.join(" ") %>>
|
4
|
+
ERB
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
@disabled = attributes[:disabled] || false
|
8
|
+
attributes[:class] = "border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm#{" #{attributes[:class]}" if attributes[:class]}"
|
9
|
+
@attributes = attributes.without(:disabled).map { |key, attribute| "#{key}=\"#{attribute}\"" }
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Auth::AuthenticatedSessionController < ApplicationController
|
2
|
+
skip_authentication only: %i[new create]
|
3
|
+
|
4
|
+
layout "guest"
|
5
|
+
|
6
|
+
def new
|
7
|
+
@form = Auth::LoginForm.new
|
8
|
+
|
9
|
+
render "auth/login"
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
@form = Auth::LoginForm.new params.permit(:email, :password)
|
14
|
+
|
15
|
+
user = @form.authenticate
|
16
|
+
|
17
|
+
return render "auth/login", status: :unprocessable_entity if user.nil?
|
18
|
+
|
19
|
+
login user
|
20
|
+
|
21
|
+
redirect_to dashboard_path
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy
|
25
|
+
logout
|
26
|
+
|
27
|
+
redirect_to login_path
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Auth::NewPasswordController < ApplicationController
|
2
|
+
skip_authentication
|
3
|
+
|
4
|
+
layout "guest"
|
5
|
+
|
6
|
+
def new
|
7
|
+
@form = Auth::NewPasswordForm.new params.permit(:token)
|
8
|
+
|
9
|
+
render "auth/reset_password"
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
@form = Auth::NewPasswordForm.new params.permit(:token, :password, :password_confirmation)
|
14
|
+
|
15
|
+
return redirect_to login_path, flash: { status: "Your password has been reset." } if @form.reset?
|
16
|
+
|
17
|
+
render "auth/reset_password", status: :unprocessable_entity
|
18
|
+
end
|
19
|
+
end
|