kaze 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +42 -0
  4. data/bin/kaze +11 -0
  5. data/lib/kaze/commands/install_command.rb +63 -0
  6. data/lib/kaze/commands/install_inertia_stacks.rb +151 -0
  7. data/lib/kaze/commands.rb +2 -0
  8. data/lib/kaze/version.rb +3 -0
  9. data/lib/kaze.rb +9 -0
  10. data/stubs/default/Procfile.dev +3 -0
  11. data/stubs/default/app/assets/stylesheets/application.css +1 -0
  12. data/stubs/default/app/assets/stylesheets/application.tailwind.css +3 -0
  13. data/stubs/default/app/controllers/application_controller.rb +5 -0
  14. data/stubs/default/app/controllers/auth/authenticated_session_controller.rb +28 -0
  15. data/stubs/default/app/controllers/auth/new_password_controller.rb +18 -0
  16. data/stubs/default/app/controllers/auth/password_reset_link_controller.rb +17 -0
  17. data/stubs/default/app/controllers/auth/registered_user_controller.rb +19 -0
  18. data/stubs/default/app/controllers/concerns/authenticate.rb +34 -0
  19. data/stubs/default/app/controllers/concerns/handle_inertia_requests.rb +9 -0
  20. data/stubs/default/app/controllers/concerns/verify_csrf_token.rb +24 -0
  21. data/stubs/default/app/controllers/dashboard_controller.rb +5 -0
  22. data/stubs/default/app/controllers/password_controller.rb +11 -0
  23. data/stubs/default/app/controllers/profile_controller.rb +31 -0
  24. data/stubs/default/app/controllers/welcome_controller.rb +10 -0
  25. data/stubs/default/app/forms/application_form.rb +9 -0
  26. data/stubs/default/app/forms/auth/login_form.rb +18 -0
  27. data/stubs/default/app/forms/auth/new_password_form.rb +21 -0
  28. data/stubs/default/app/forms/auth/register_form.rb +7 -0
  29. data/stubs/default/app/forms/auth/send_password_reset_link_form.rb +22 -0
  30. data/stubs/default/app/forms/delete_user_form.rb +5 -0
  31. data/stubs/default/app/forms/update_password_form.rb +6 -0
  32. data/stubs/default/app/forms/update_profile_information_form.rb +6 -0
  33. data/stubs/default/app/mailers/application_mailer.rb +11 -0
  34. data/stubs/default/app/mailers/user_mailer.rb +8 -0
  35. data/stubs/default/app/models/application_record.rb +3 -0
  36. data/stubs/default/app/models/concerns/can_reset_password.rb +5 -0
  37. data/stubs/default/app/models/current.rb +3 -0
  38. data/stubs/default/app/models/user.rb +11 -0
  39. data/stubs/default/app/validators/current_password_validator.rb +5 -0
  40. data/stubs/default/app/validators/email_validator.rb +7 -0
  41. data/stubs/default/app/validators/lowercase_validator.rb +5 -0
  42. data/stubs/default/app/validators/uniqueness_validator.rb +24 -0
  43. data/stubs/default/app/views/layouts/mailer.html.erb +374 -0
  44. data/stubs/default/app/views/layouts/mailer.text.erb +11 -0
  45. data/stubs/default/app/views/user_mailer/reset_password.html.erb +39 -0
  46. data/stubs/default/bin/dev +16 -0
  47. data/stubs/default/bin/vite +27 -0
  48. data/stubs/default/config/routes.rb +27 -0
  49. data/stubs/default/config/vite.json +16 -0
  50. data/stubs/default/db/migrate/20240101000000_create_users.rb +14 -0
  51. data/stubs/default/db/migrate/20240101000001_create_delayed_jobs.rb +22 -0
  52. data/stubs/inertia-react-ts/app/javascript/Components/ApplicationLogo.tsx +12 -0
  53. data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +14 -0
  54. data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +17 -0
  55. data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +99 -0
  56. data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +9 -0
  57. data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +9 -0
  58. data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +68 -0
  59. data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +18 -0
  60. data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +17 -0
  61. data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +16 -0
  62. data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +18 -0
  63. data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +30 -0
  64. data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +131 -0
  65. data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +19 -0
  66. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +52 -0
  67. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +98 -0
  68. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +118 -0
  69. data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +74 -0
  70. data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +22 -0
  71. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +33 -0
  72. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +100 -0
  73. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +114 -0
  74. data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +84 -0
  75. data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +66 -0
  76. data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +34 -0
  77. data/stubs/inertia-react-ts/app/javascript/entrypoints/bootstrap.ts +4 -0
  78. data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +7 -0
  79. data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +12 -0
  80. data/stubs/inertia-react-ts/app/javascript/types/vite-env.d.ts +1 -0
  81. data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +26 -0
  82. data/stubs/inertia-react-ts/config/tailwind.config.js +22 -0
  83. data/stubs/inertia-react-ts/package.json +26 -0
  84. data/stubs/inertia-react-ts/tsconfig.json +19 -0
  85. data/stubs/inertia-react-ts/vite.config.ts +13 -0
  86. data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +8 -0
  87. data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +29 -0
  88. data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +7 -0
  89. data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +75 -0
  90. data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +16 -0
  91. data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +13 -0
  92. data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +12 -0
  93. data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +96 -0
  94. data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +21 -0
  95. data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +7 -0
  96. data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +21 -0
  97. data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +19 -0
  98. data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +23 -0
  99. data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +155 -0
  100. data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +20 -0
  101. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +60 -0
  102. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +93 -0
  103. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +106 -0
  104. data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +89 -0
  105. data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +22 -0
  106. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +42 -0
  107. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +98 -0
  108. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +108 -0
  109. data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +78 -0
  110. data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +56 -0
  111. data/stubs/inertia-vue-ts/app/javascript/entrypoints/application.ts +34 -0
  112. data/stubs/inertia-vue-ts/app/javascript/entrypoints/bootstrap.ts +4 -0
  113. data/stubs/inertia-vue-ts/app/javascript/types/global.d.ts +13 -0
  114. data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +12 -0
  115. data/stubs/inertia-vue-ts/app/javascript/types/vite-env.d.ts +1 -0
  116. data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +25 -0
  117. data/stubs/inertia-vue-ts/config/tailwind.config.js +22 -0
  118. data/stubs/inertia-vue-ts/package.json +24 -0
  119. data/stubs/inertia-vue-ts/tsconfig.json +19 -0
  120. data/stubs/inertia-vue-ts/vite.config.ts +13 -0
  121. metadata +205 -0
@@ -0,0 +1,6 @@
1
+ class UpdatePasswordForm < ApplicationForm
2
+ attr_accessor :current_password, :password, :password_confirmation
3
+
4
+ validates :current_password, presence: true, current_password: true
5
+ validates :password, presence: true, confirmation: true, length: { minimum: 8 }
6
+ end
@@ -0,0 +1,6 @@
1
+ class UpdateProfileInformationForm < ApplicationForm
2
+ attr_accessor :name, :email
3
+
4
+ validates :name, presence: true
5
+ validates :email, presence: true, lowercase: true, email: true, uniqueness: { model: User, attribute: :email, conditions: -> { where.not(id: Current.user.id) } }
6
+ end
@@ -0,0 +1,11 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: email_address_with_name(
3
+ ENV.fetch("MAIL_FROM_ADDRESS", "hello@example.com"),
4
+ ENV.fetch("MAIL_FROM_NAME", "Example")
5
+ )
6
+ layout "mailer"
7
+
8
+ def default_url_options
9
+ { host: ENV.fetch("APP_URL", "http://localhost") }
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ class UserMailer < ApplicationMailer
2
+ def reset_password
3
+ mail(
4
+ to: params[:user].email,
5
+ subject: "Reset Password Notification"
6
+ )
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ primary_abstract_class
3
+ end
@@ -0,0 +1,5 @@
1
+ module CanResetPassword
2
+ def send_password_reset_notification(token)
3
+ UserMailer.with(user: self, token: token).reset_password.deliver_later
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ class Current < ActiveSupport::CurrentAttributes
2
+ attribute :user
3
+ end
@@ -0,0 +1,11 @@
1
+ class User < ApplicationRecord
2
+ include CanResetPassword
3
+
4
+ has_secure_password
5
+
6
+ normalizes :email, with: ->(email) { email.strip.downcase }
7
+
8
+ generates_token_for :password_reset, expires_in: 60.minutes do
9
+ password_salt&.last(10)
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class CurrentPasswordValidator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ record.errors.add attribute, "The password is incorrect." unless Current.user&.authenticate(value)
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class EmailValidator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ unless URI::MailTo::EMAIL_REGEXP.match?(value)
4
+ record.errors.add attribute, (options[:message] || "is not an email")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class LowercaseValidator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ record.errors.add attribute, (options[:message] || "must be lowercase") unless value == value.downcase
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ class UniquenessValidator < ActiveRecord::Validations::UniquenessValidator
2
+ def initialize(klass)
3
+ super
4
+ @klass = options[:model] if options[:model]
5
+ end
6
+
7
+ def validate_each(record, attribute, value)
8
+ if !options[:model] && !record.class.ancestors.include?(ActiveRecord::Base)
9
+ raise ArgumentError, "Unknown validator: 'UniquenessValidator'"
10
+ end
11
+
12
+ super unless options[:model]
13
+
14
+ record_org = record
15
+ attribute_org = attribute
16
+
17
+ attribute = options[:attribute].to_sym if options[:attribute]
18
+ record = options[:model].new(attribute => value)
19
+
20
+ super
21
+
22
+ record_org.errors.add(attribute_org, :taken) if record.errors.any?
23
+ end
24
+ end
@@ -0,0 +1,374 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <title><%= ENV.fetch("APP_NAME", "Rails") %></title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7
+ <meta name="color-scheme" content="light">
8
+ <meta name="supported-color-schemes" content="light">
9
+ <style>
10
+ /* Base */
11
+
12
+ body,
13
+ body *:not(html):not(style):not(br):not(tr):not(code) {
14
+ box-sizing: border-box;
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
16
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
17
+ position: relative;
18
+ }
19
+
20
+ body {
21
+ -webkit-text-size-adjust: none;
22
+ background-color: #ffffff;
23
+ color: #718096;
24
+ height: 100%;
25
+ line-height: 1.4;
26
+ margin: 0;
27
+ padding: 0;
28
+ width: 100% !important;
29
+ }
30
+
31
+ p,
32
+ ul,
33
+ ol,
34
+ blockquote {
35
+ line-height: 1.4;
36
+ text-align: left;
37
+ }
38
+
39
+ a {
40
+ color: #3869d4;
41
+ }
42
+
43
+ a img {
44
+ border: none;
45
+ }
46
+
47
+ /* Typography */
48
+
49
+ h1 {
50
+ color: #3d4852;
51
+ font-size: 18px;
52
+ font-weight: bold;
53
+ margin-top: 0;
54
+ text-align: left;
55
+ }
56
+
57
+ h2 {
58
+ font-size: 16px;
59
+ font-weight: bold;
60
+ margin-top: 0;
61
+ text-align: left;
62
+ }
63
+
64
+ h3 {
65
+ font-size: 14px;
66
+ font-weight: bold;
67
+ margin-top: 0;
68
+ text-align: left;
69
+ }
70
+
71
+ p {
72
+ font-size: 16px;
73
+ line-height: 1.5em;
74
+ margin-top: 0;
75
+ text-align: left;
76
+ }
77
+
78
+ p.sub {
79
+ font-size: 12px;
80
+ }
81
+
82
+ img {
83
+ max-width: 100%;
84
+ }
85
+
86
+ /* Layout */
87
+
88
+ .wrapper {
89
+ -premailer-cellpadding: 0;
90
+ -premailer-cellspacing: 0;
91
+ -premailer-width: 100%;
92
+ background-color: #edf2f7;
93
+ margin: 0;
94
+ padding: 0;
95
+ width: 100%;
96
+ }
97
+
98
+ .content {
99
+ -premailer-cellpadding: 0;
100
+ -premailer-cellspacing: 0;
101
+ -premailer-width: 100%;
102
+ margin: 0;
103
+ padding: 0;
104
+ width: 100%;
105
+ }
106
+
107
+ /* Header */
108
+
109
+ .header {
110
+ padding: 25px 0;
111
+ text-align: center;
112
+ }
113
+
114
+ .header a {
115
+ color: #3d4852;
116
+ font-size: 19px;
117
+ font-weight: bold;
118
+ text-decoration: none;
119
+ }
120
+
121
+ /* Logo */
122
+
123
+ .logo {
124
+ height: 75px;
125
+ max-height: 75px;
126
+ width: 75px;
127
+ }
128
+
129
+ /* Body */
130
+
131
+ .body {
132
+ -premailer-cellpadding: 0;
133
+ -premailer-cellspacing: 0;
134
+ -premailer-width: 100%;
135
+ background-color: #edf2f7;
136
+ border-bottom: 1px solid #edf2f7;
137
+ border-top: 1px solid #edf2f7;
138
+ margin: 0;
139
+ padding: 0;
140
+ width: 100%;
141
+ }
142
+
143
+ .inner-body {
144
+ -premailer-cellpadding: 0;
145
+ -premailer-cellspacing: 0;
146
+ -premailer-width: 570px;
147
+ background-color: #ffffff;
148
+ border-color: #e8e5ef;
149
+ border-radius: 2px;
150
+ border-width: 1px;
151
+ box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);
152
+ margin: 0 auto;
153
+ padding: 0;
154
+ width: 570px;
155
+ }
156
+
157
+ /* Subcopy */
158
+
159
+ .subcopy {
160
+ border-top: 1px solid #e8e5ef;
161
+ margin-top: 25px;
162
+ padding-top: 25px;
163
+ }
164
+
165
+ .subcopy p {
166
+ font-size: 14px;
167
+ }
168
+
169
+ /* Footer */
170
+
171
+ .footer {
172
+ -premailer-cellpadding: 0;
173
+ -premailer-cellspacing: 0;
174
+ -premailer-width: 570px;
175
+ margin: 0 auto;
176
+ padding: 0;
177
+ text-align: center;
178
+ width: 570px;
179
+ }
180
+
181
+ .footer p {
182
+ color: #b0adc5;
183
+ font-size: 12px;
184
+ text-align: center;
185
+ }
186
+
187
+ .footer a {
188
+ color: #b0adc5;
189
+ text-decoration: underline;
190
+ }
191
+
192
+ /* Tables */
193
+
194
+ .table table {
195
+ -premailer-cellpadding: 0;
196
+ -premailer-cellspacing: 0;
197
+ -premailer-width: 100%;
198
+ margin: 30px auto;
199
+ width: 100%;
200
+ }
201
+
202
+ .table th {
203
+ border-bottom: 1px solid #edeff2;
204
+ margin: 0;
205
+ padding-bottom: 8px;
206
+ }
207
+
208
+ .table td {
209
+ color: #74787e;
210
+ font-size: 15px;
211
+ line-height: 18px;
212
+ margin: 0;
213
+ padding: 10px 0;
214
+ }
215
+
216
+ .content-cell {
217
+ max-width: 100vw;
218
+ padding: 32px;
219
+ }
220
+
221
+ /* Buttons */
222
+
223
+ .action {
224
+ -premailer-cellpadding: 0;
225
+ -premailer-cellspacing: 0;
226
+ -premailer-width: 100%;
227
+ margin: 30px auto;
228
+ padding: 0;
229
+ text-align: center;
230
+ width: 100%;
231
+ }
232
+
233
+ .button {
234
+ -webkit-text-size-adjust: none;
235
+ border-radius: 4px;
236
+ color: #fff;
237
+ display: inline-block;
238
+ overflow: hidden;
239
+ text-decoration: none;
240
+ }
241
+
242
+ .button-blue,
243
+ .button-primary {
244
+ background-color: #2d3748;
245
+ border-bottom: 8px solid #2d3748;
246
+ border-left: 18px solid #2d3748;
247
+ border-right: 18px solid #2d3748;
248
+ border-top: 8px solid #2d3748;
249
+ }
250
+
251
+ .button-green,
252
+ .button-success {
253
+ background-color: #48bb78;
254
+ border-bottom: 8px solid #48bb78;
255
+ border-left: 18px solid #48bb78;
256
+ border-right: 18px solid #48bb78;
257
+ border-top: 8px solid #48bb78;
258
+ }
259
+
260
+ .button-red,
261
+ .button-error {
262
+ background-color: #e53e3e;
263
+ border-bottom: 8px solid #e53e3e;
264
+ border-left: 18px solid #e53e3e;
265
+ border-right: 18px solid #e53e3e;
266
+ border-top: 8px solid #e53e3e;
267
+ }
268
+
269
+ /* Panels */
270
+
271
+ .panel {
272
+ border-left: #2d3748 solid 4px;
273
+ margin: 21px 0;
274
+ }
275
+
276
+ .panel-content {
277
+ background-color: #edf2f7;
278
+ color: #718096;
279
+ padding: 16px;
280
+ }
281
+
282
+ .panel-content p {
283
+ color: #718096;
284
+ }
285
+
286
+ .panel-item {
287
+ padding: 0;
288
+ }
289
+
290
+ .panel-item p:last-of-type {
291
+ margin-bottom: 0;
292
+ padding-bottom: 0;
293
+ }
294
+
295
+ /* Utilities */
296
+
297
+ .break-all {
298
+ word-break: break-all;
299
+ }
300
+
301
+ @media only screen and (max-width: 600px) {
302
+ .inner-body {
303
+ width: 100% !important;
304
+ }
305
+
306
+ .footer {
307
+ width: 100% !important;
308
+ }
309
+ }
310
+
311
+ @media only screen and (max-width: 500px) {
312
+ .button {
313
+ width: 100% !important;
314
+ }
315
+ }
316
+ </style>
317
+ </head>
318
+ <body>
319
+
320
+ <table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
321
+ <tr>
322
+ <td align="center">
323
+ <table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
324
+ <!-- Header -->
325
+ <tr>
326
+ <td class="header">
327
+ <a href="<%= root_url %>" style="display: inline-block;">
328
+ <%= ENV.fetch("APP_NAME", "Rails") %>
329
+ </a>
330
+ </td>
331
+ </tr>
332
+
333
+ <!-- Email Body -->
334
+ <tr>
335
+ <td class="body" width="100%" cellpadding="0" cellspacing="0" style="border: hidden !important;">
336
+ <table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
337
+ <!-- Body content -->
338
+ <tr>
339
+ <td class="content-cell">
340
+ <%= yield %>
341
+
342
+ <!-- Subcopy -->
343
+ <table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
344
+ <tr>
345
+ <td>
346
+ <%= yield(:subcopy) %>
347
+ </td>
348
+ </tr>
349
+ </table>
350
+
351
+ </td>
352
+ </tr>
353
+ </table>
354
+ </td>
355
+ </tr>
356
+
357
+ <!-- Footer -->
358
+ <tr>
359
+ <td>
360
+ <table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
361
+ <tr>
362
+ <td class="content-cell" align="center">
363
+ © <%= Time.new.year %> <%= ENV.fetch("APP_NAME", "Rails") %>. All rights reserved.
364
+ </td>
365
+ </tr>
366
+ </table>
367
+ </td>
368
+ </tr>
369
+ </table>
370
+ </td>
371
+ </tr>
372
+ </table>
373
+ </body>
374
+ </html>
@@ -0,0 +1,11 @@
1
+ <!-- Header -->
2
+ <%= ENV.fetch("APP_NAME", "Rails") %>: <%= root_url %>
3
+
4
+ <!-- Body -->
5
+ <%= yield %>
6
+
7
+ <!-- Subcopy -->
8
+ <%= yield(:subcopy) %>
9
+
10
+ <!-- Footer -->
11
+ © <%= Time.new.year %> <%= ENV.fetch("APP_NAME", "Rails") %>. All rights reserved.
@@ -0,0 +1,39 @@
1
+ <!-- Greeting -->
2
+ <h1>Hello!</h1>
3
+
4
+ <!-- Intro Lines -->
5
+ <p>You are receiving this email because we received a password reset request for your account.</p>
6
+
7
+ <!-- Action Button -->
8
+ <table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
9
+ <tr>
10
+ <td align="center">
11
+ <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
12
+ <tr>
13
+ <td align="center">
14
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation">
15
+ <tr>
16
+ <td>
17
+ <a href="<%= password_reset_url(token: params[:token]) %>" class="button button-primary" target="_blank" rel="noopener">Reset Password</a>
18
+ </td>
19
+ </tr>
20
+ </table>
21
+ </td>
22
+ </tr>
23
+ </table>
24
+ </td>
25
+ </tr>
26
+ </table>
27
+
28
+ <!-- Outro Lines -->
29
+ <p>This password reset link will expire in 60 minutes.</p>
30
+
31
+ <p>If you did not request a password reset, no further action is required.</p>
32
+
33
+ <!-- Salutation -->
34
+ <p>Regards,<br><%= ENV.fetch("APP_NAME", "Rails") %></p>
35
+
36
+ <!-- Subcopy -->
37
+ <% content_for :subcopy do %>
38
+ <p>If you're having trouble clicking the "Reset Password" button, copy and paste the URL below into your web browser: <span class="break-all"><%= link_to password_reset_url(token: params[:token]), password_reset_url(token: params[:token]) %><span></p>
39
+ <% end %>
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env sh
2
+
3
+ if ! gem list foreman -i --silent; then
4
+ echo "Installing foreman..."
5
+ gem install foreman
6
+ fi
7
+
8
+ # Default to port 3000 if not specified
9
+ export PORT="${PORT:-3000}"
10
+
11
+ # Let the debug gem allow remote connections,
12
+ # but avoid loading until `debugger` is called
13
+ export RUBY_DEBUG_OPEN="true"
14
+ export RUBY_DEBUG_LAZY="true"
15
+
16
+ exec foreman start -f Procfile.dev "$@"
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'vite' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("vite_ruby", "vite")
@@ -0,0 +1,27 @@
1
+ Rails.application.routes.draw do
2
+ get "up" => "rails/health#show", as: :rails_health_check
3
+
4
+ root "welcome#index"
5
+
6
+ get "register", to: "auth/registered_user#new", as: :register
7
+ post "register", to: "auth/registered_user#create"
8
+
9
+ get "login", to: "auth/authenticated_session#new", as: :login
10
+ post "login", to: "auth/authenticated_session#create"
11
+
12
+ get "forgot-password", to: "auth/password_reset_link#new", as: :password_request
13
+ post "forgot-password", to: "auth/password_reset_link#create", as: :password_email
14
+
15
+ get "reset-password/:token", to: "auth/new_password#new", as: :password_reset
16
+ post "reset-password", to: "auth/new_password#create", as: :password_store
17
+
18
+ post "logout", to: "auth/authenticated_session#destroy", as: :logout
19
+
20
+ get "dashboard", to: "dashboard#show", as: :dashboard
21
+
22
+ get "profile", to: "profile#edit", as: :profile_edit
23
+ patch "profile", to: "profile#update", as: :profile_update
24
+ delete "profile", to: "profile#destroy", as: :profile_destroy
25
+
26
+ put "password", to: "password#update", as: :password_update
27
+ end
@@ -0,0 +1,16 @@
1
+ {
2
+ "all": {
3
+ "sourceCodeDir": "app/javascript",
4
+ "watchAdditionalPaths": []
5
+ },
6
+ "development": {
7
+ "autoBuild": true,
8
+ "publicOutputDir": "vite-dev",
9
+ "port": 3036
10
+ },
11
+ "test": {
12
+ "autoBuild": true,
13
+ "publicOutputDir": "vite-test",
14
+ "port": 3037
15
+ }
16
+ }
@@ -0,0 +1,14 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |table|
4
+ table.string :name
5
+ table.string :email
6
+ table.string :password_digest
7
+ table.timestamps
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :users
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ class CreateDelayedJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :delayed_jobs do |table|
4
+ table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
5
+ table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
6
+ table.text :handler, null: false # YAML-encoded string of the object that will do work
7
+ table.text :last_error # reason for last failure (See Note below)
8
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9
+ table.datetime :locked_at # Set when a client is working on this object
10
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
+ table.string :locked_by # Who is working on this object (if locked)
12
+ table.string :queue # The name of the queue this job is in
13
+ table.timestamps null: true
14
+ end
15
+
16
+ add_index :delayed_jobs, [ :priority, :run_at ], name: "delayed_jobs_priority"
17
+ end
18
+
19
+ def self.down
20
+ drop_table :delayed_jobs
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ import { SVGAttributes } from 'react';
2
+
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
+ );
12
+ }