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,118 @@
1
+ import { useEffect, FormEventHandler } from 'react';
2
+ import GuestLayout from '@/Layouts/GuestLayout';
3
+ import InputError from '@/Components/InputError';
4
+ import InputLabel from '@/Components/InputLabel';
5
+ import PrimaryButton from '@/Components/PrimaryButton';
6
+ import TextInput from '@/Components/TextInput';
7
+ import { Head, Link, useForm } from '@inertiajs/react';
8
+ import { login_path, register_path } from '@/routes';
9
+
10
+ export default function Register() {
11
+ const { data, setData, post, processing, errors, reset } = useForm({
12
+ name: '',
13
+ email: '',
14
+ password: '',
15
+ password_confirmation: '',
16
+ });
17
+
18
+ useEffect(() => {
19
+ return () => {
20
+ reset('password', 'password_confirmation');
21
+ };
22
+ }, []);
23
+
24
+ const submit: FormEventHandler = (e) => {
25
+ e.preventDefault();
26
+
27
+ post(register_path());
28
+ };
29
+
30
+ return (
31
+ <GuestLayout>
32
+ <Head title="Register" />
33
+
34
+ <form onSubmit={submit}>
35
+ <div>
36
+ <InputLabel htmlFor="name" value="Name" />
37
+
38
+ <TextInput
39
+ id="name"
40
+ name="name"
41
+ value={data.name}
42
+ className="mt-1 block w-full"
43
+ autoComplete="name"
44
+ isFocused={true}
45
+ onChange={(e) => setData('name', e.target.value)}
46
+ required
47
+ />
48
+
49
+ <InputError message={errors.name} className="mt-2" />
50
+ </div>
51
+
52
+ <div className="mt-4">
53
+ <InputLabel htmlFor="email" value="Email" />
54
+
55
+ <TextInput
56
+ id="email"
57
+ type="email"
58
+ name="email"
59
+ value={data.email}
60
+ className="mt-1 block w-full"
61
+ autoComplete="username"
62
+ onChange={(e) => setData('email', e.target.value)}
63
+ required
64
+ />
65
+
66
+ <InputError message={errors.email} className="mt-2" />
67
+ </div>
68
+
69
+ <div className="mt-4">
70
+ <InputLabel htmlFor="password" value="Password" />
71
+
72
+ <TextInput
73
+ id="password"
74
+ type="password"
75
+ name="password"
76
+ value={data.password}
77
+ className="mt-1 block w-full"
78
+ autoComplete="new-password"
79
+ onChange={(e) => setData('password', e.target.value)}
80
+ required
81
+ />
82
+
83
+ <InputError message={errors.password} className="mt-2" />
84
+ </div>
85
+
86
+ <div className="mt-4">
87
+ <InputLabel htmlFor="password_confirmation" value="Confirm Password" />
88
+
89
+ <TextInput
90
+ id="password_confirmation"
91
+ type="password"
92
+ name="password_confirmation"
93
+ value={data.password_confirmation}
94
+ className="mt-1 block w-full"
95
+ autoComplete="new-password"
96
+ onChange={(e) => setData('password_confirmation', e.target.value)}
97
+ required
98
+ />
99
+
100
+ <InputError message={errors.password_confirmation} className="mt-2" />
101
+ </div>
102
+
103
+ <div className="flex items-center justify-end mt-4">
104
+ <Link
105
+ href={login_path()}
106
+ className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
107
+ >
108
+ Already registered?
109
+ </Link>
110
+
111
+ <PrimaryButton className="ms-4" disabled={processing}>
112
+ Register
113
+ </PrimaryButton>
114
+ </div>
115
+ </form>
116
+ </GuestLayout>
117
+ );
118
+ }
@@ -0,0 +1,74 @@
1
+ import { useEffect, FormEventHandler } from 'react';
2
+ import GuestLayout from '@/Layouts/GuestLayout';
3
+ import InputError from '@/Components/InputError';
4
+ import InputLabel from '@/Components/InputLabel';
5
+ import PrimaryButton from '@/Components/PrimaryButton';
6
+ import TextInput from '@/Components/TextInput';
7
+ import { Head, useForm } from '@inertiajs/react';
8
+ import { password_store_path } from '@/routes';
9
+
10
+ export default function ResetPassword({ token }: { token: string }) {
11
+ const { data, setData, post, processing, errors, reset } = useForm({
12
+ token: token,
13
+ password: '',
14
+ password_confirmation: '',
15
+ });
16
+
17
+ useEffect(() => {
18
+ return () => {
19
+ reset('password', 'password_confirmation');
20
+ };
21
+ }, []);
22
+
23
+ const submit: FormEventHandler = (e) => {
24
+ e.preventDefault();
25
+
26
+ post(password_store_path());
27
+ };
28
+
29
+ return (
30
+ <GuestLayout>
31
+ <Head title="Reset Password" />
32
+
33
+ <form onSubmit={submit}>
34
+ <div className="mt-4">
35
+ <InputLabel htmlFor="password" value="Password" />
36
+
37
+ <TextInput
38
+ id="password"
39
+ type="password"
40
+ name="password"
41
+ value={data.password}
42
+ className="mt-1 block w-full"
43
+ autoComplete="new-password"
44
+ isFocused={true}
45
+ onChange={(e) => setData('password', e.target.value)}
46
+ />
47
+
48
+ <InputError message={errors.password} className="mt-2" />
49
+ </div>
50
+
51
+ <div className="mt-4">
52
+ <InputLabel htmlFor="password_confirmation" value="Confirm Password" />
53
+
54
+ <TextInput
55
+ type="password"
56
+ name="password_confirmation"
57
+ value={data.password_confirmation}
58
+ className="mt-1 block w-full"
59
+ autoComplete="new-password"
60
+ onChange={(e) => setData('password_confirmation', e.target.value)}
61
+ />
62
+
63
+ <InputError message={errors.password_confirmation} className="mt-2" />
64
+ </div>
65
+
66
+ <div className="flex items-center justify-end mt-4">
67
+ <PrimaryButton className="ms-4" disabled={processing}>
68
+ Reset Password
69
+ </PrimaryButton>
70
+ </div>
71
+ </form>
72
+ </GuestLayout>
73
+ );
74
+ }
@@ -0,0 +1,22 @@
1
+ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2
+ import { Head } from '@inertiajs/react';
3
+ import { PageProps } from '@/types';
4
+
5
+ export default function Dashboard({ auth }: PageProps) {
6
+ return (
7
+ <AuthenticatedLayout
8
+ user={auth.user}
9
+ header={<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">Dashboard</h2>}
10
+ >
11
+ <Head title="Dashboard" />
12
+
13
+ <div className="py-12">
14
+ <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
15
+ <div className="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
16
+ <div className="p-6 text-gray-900 dark:text-gray-100">You're logged in!</div>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </AuthenticatedLayout>
21
+ );
22
+ }
@@ -0,0 +1,33 @@
1
+ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
2
+ import DeleteUserForm from './Partials/DeleteUserForm';
3
+ import UpdatePasswordForm from './Partials/UpdatePasswordForm';
4
+ import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm';
5
+ import { Head } from '@inertiajs/react';
6
+ import { PageProps } from '@/types';
7
+
8
+ export default function Edit({ auth }: PageProps) {
9
+ return (
10
+ <AuthenticatedLayout
11
+ user={auth.user}
12
+ header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Profile</h2>}
13
+ >
14
+ <Head title="Profile" />
15
+
16
+ <div className="py-12">
17
+ <div className="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
18
+ <div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
19
+ <UpdateProfileInformationForm className="max-w-xl" />
20
+ </div>
21
+
22
+ <div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
23
+ <UpdatePasswordForm className="max-w-xl" />
24
+ </div>
25
+
26
+ <div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
27
+ <DeleteUserForm className="max-w-xl" />
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </AuthenticatedLayout>
32
+ );
33
+ }
@@ -0,0 +1,100 @@
1
+ import { useRef, useState, FormEventHandler } from 'react';
2
+ import DangerButton from '@/Components/DangerButton';
3
+ import InputError from '@/Components/InputError';
4
+ import InputLabel from '@/Components/InputLabel';
5
+ import Modal from '@/Components/Modal';
6
+ import SecondaryButton from '@/Components/SecondaryButton';
7
+ import TextInput from '@/Components/TextInput';
8
+ import { useForm } from '@inertiajs/react';
9
+ import { profile_destroy_path } from '@/routes';
10
+
11
+ export default function DeleteUserForm({ className = '' }: { className?: string }) {
12
+ const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
13
+ const passwordInput = useRef<HTMLInputElement>(null);
14
+
15
+ const {
16
+ data,
17
+ setData,
18
+ delete: destroy,
19
+ processing,
20
+ reset,
21
+ errors,
22
+ } = useForm({
23
+ password: '',
24
+ });
25
+
26
+ const confirmUserDeletion = () => {
27
+ setConfirmingUserDeletion(true);
28
+ };
29
+
30
+ const deleteUser: FormEventHandler = (e) => {
31
+ e.preventDefault();
32
+
33
+ destroy(profile_destroy_path(), {
34
+ preserveScroll: true,
35
+ onSuccess: () => closeModal(),
36
+ onError: () => passwordInput.current?.focus(),
37
+ onFinish: () => reset(),
38
+ });
39
+ };
40
+
41
+ const closeModal = () => {
42
+ setConfirmingUserDeletion(false);
43
+
44
+ reset();
45
+ };
46
+
47
+ return (
48
+ <section className={`space-y-6 ${className}`}>
49
+ <header>
50
+ <h2 className="text-lg font-medium text-gray-900">Delete Account</h2>
51
+
52
+ <p className="mt-1 text-sm text-gray-600">
53
+ Once your account is deleted, all of its resources and data will be permanently deleted. Before
54
+ deleting your account, please download any data or information that you wish to retain.
55
+ </p>
56
+ </header>
57
+
58
+ <DangerButton onClick={confirmUserDeletion}>Delete Account</DangerButton>
59
+
60
+ <Modal show={confirmingUserDeletion} onClose={closeModal}>
61
+ <form onSubmit={deleteUser} className="p-6">
62
+ <h2 className="text-lg font-medium text-gray-900">
63
+ Are you sure you want to delete your account?
64
+ </h2>
65
+
66
+ <p className="mt-1 text-sm text-gray-600">
67
+ Once your account is deleted, all of its resources and data will be permanently deleted. Please
68
+ enter your password to confirm you would like to permanently delete your account.
69
+ </p>
70
+
71
+ <div className="mt-6">
72
+ <InputLabel htmlFor="password" value="Password" className="sr-only" />
73
+
74
+ <TextInput
75
+ id="password"
76
+ type="password"
77
+ name="password"
78
+ ref={passwordInput}
79
+ value={data.password}
80
+ onChange={(e) => setData('password', e.target.value)}
81
+ className="mt-1 block w-3/4"
82
+ isFocused
83
+ placeholder="Password"
84
+ />
85
+
86
+ <InputError message={errors.password} className="mt-2" />
87
+ </div>
88
+
89
+ <div className="mt-6 flex justify-end">
90
+ <SecondaryButton onClick={closeModal}>Cancel</SecondaryButton>
91
+
92
+ <DangerButton className="ms-3" disabled={processing}>
93
+ Delete Account
94
+ </DangerButton>
95
+ </div>
96
+ </form>
97
+ </Modal>
98
+ </section>
99
+ );
100
+ }
@@ -0,0 +1,114 @@
1
+ import { useRef, FormEventHandler } from 'react';
2
+ import InputError from '@/Components/InputError';
3
+ import InputLabel from '@/Components/InputLabel';
4
+ import PrimaryButton from '@/Components/PrimaryButton';
5
+ import TextInput from '@/Components/TextInput';
6
+ import { useForm } from '@inertiajs/react';
7
+ import { Transition } from '@headlessui/react';
8
+ import { password_update_path } from '@/routes';
9
+
10
+ export default function UpdatePasswordForm({ className = '' }: { className?: string }) {
11
+ const passwordInput = useRef<HTMLInputElement>(null);
12
+ const currentPasswordInput = useRef<HTMLInputElement>(null);
13
+
14
+ const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
15
+ current_password: '',
16
+ password: '',
17
+ password_confirmation: '',
18
+ });
19
+
20
+ const updatePassword: FormEventHandler = (e) => {
21
+ e.preventDefault();
22
+
23
+ put(password_update_path(), {
24
+ preserveScroll: true,
25
+ onSuccess: () => reset(),
26
+ onError: (errors) => {
27
+ if (errors.password) {
28
+ reset('password', 'password_confirmation');
29
+ passwordInput.current?.focus();
30
+ }
31
+
32
+ if (errors.current_password) {
33
+ reset('current_password');
34
+ currentPasswordInput.current?.focus();
35
+ }
36
+ },
37
+ });
38
+ };
39
+
40
+ return (
41
+ <section className={className}>
42
+ <header>
43
+ <h2 className="text-lg font-medium text-gray-900">Update Password</h2>
44
+
45
+ <p className="mt-1 text-sm text-gray-600">
46
+ Ensure your account is using a long, random password to stay secure.
47
+ </p>
48
+ </header>
49
+
50
+ <form onSubmit={updatePassword} className="mt-6 space-y-6">
51
+ <div>
52
+ <InputLabel htmlFor="current_password" value="Current Password" />
53
+
54
+ <TextInput
55
+ id="current_password"
56
+ ref={currentPasswordInput}
57
+ value={data.current_password}
58
+ onChange={(e) => setData('current_password', e.target.value)}
59
+ type="password"
60
+ className="mt-1 block w-full"
61
+ autoComplete="current-password"
62
+ />
63
+
64
+ <InputError message={errors.current_password} className="mt-2" />
65
+ </div>
66
+
67
+ <div>
68
+ <InputLabel htmlFor="password" value="New Password" />
69
+
70
+ <TextInput
71
+ id="password"
72
+ ref={passwordInput}
73
+ value={data.password}
74
+ onChange={(e) => setData('password', e.target.value)}
75
+ type="password"
76
+ className="mt-1 block w-full"
77
+ autoComplete="new-password"
78
+ />
79
+
80
+ <InputError message={errors.password} className="mt-2" />
81
+ </div>
82
+
83
+ <div>
84
+ <InputLabel htmlFor="password_confirmation" value="Confirm Password" />
85
+
86
+ <TextInput
87
+ id="password_confirmation"
88
+ value={data.password_confirmation}
89
+ onChange={(e) => setData('password_confirmation', e.target.value)}
90
+ type="password"
91
+ className="mt-1 block w-full"
92
+ autoComplete="new-password"
93
+ />
94
+
95
+ <InputError message={errors.password_confirmation} className="mt-2" />
96
+ </div>
97
+
98
+ <div className="flex items-center gap-4">
99
+ <PrimaryButton disabled={processing}>Save</PrimaryButton>
100
+
101
+ <Transition
102
+ show={recentlySuccessful}
103
+ enter="transition ease-in-out"
104
+ enterFrom="opacity-0"
105
+ leave="transition ease-in-out"
106
+ leaveTo="opacity-0"
107
+ >
108
+ <p className="text-sm text-gray-600">Saved.</p>
109
+ </Transition>
110
+ </div>
111
+ </form>
112
+ </section>
113
+ );
114
+ }
@@ -0,0 +1,84 @@
1
+ import InputError from '@/Components/InputError';
2
+ import InputLabel from '@/Components/InputLabel';
3
+ import PrimaryButton from '@/Components/PrimaryButton';
4
+ import TextInput from '@/Components/TextInput';
5
+ import { useForm, usePage } from '@inertiajs/react';
6
+ import { Transition } from '@headlessui/react';
7
+ import { FormEventHandler } from 'react';
8
+ import { PageProps } from '@/types';
9
+ import { profile_update_path } from '@/routes';
10
+
11
+ export default function UpdateProfileInformation({ className = '' }: { className?: string }) {
12
+ const user = usePage<PageProps>().props.auth.user;
13
+
14
+ const { data, setData, patch, errors, processing, recentlySuccessful } = useForm({
15
+ name: user.name,
16
+ email: user.email,
17
+ });
18
+
19
+ const submit: FormEventHandler = (e) => {
20
+ e.preventDefault();
21
+
22
+ patch(profile_update_path());
23
+ };
24
+
25
+ return (
26
+ <section className={className}>
27
+ <header>
28
+ <h2 className="text-lg font-medium text-gray-900">Profile Information</h2>
29
+
30
+ <p className="mt-1 text-sm text-gray-600">
31
+ Update your account's profile information and email address.
32
+ </p>
33
+ </header>
34
+
35
+ <form onSubmit={submit} className="mt-6 space-y-6">
36
+ <div>
37
+ <InputLabel htmlFor="name" value="Name" />
38
+
39
+ <TextInput
40
+ id="name"
41
+ className="mt-1 block w-full"
42
+ value={data.name}
43
+ onChange={(e) => setData('name', e.target.value)}
44
+ required
45
+ isFocused
46
+ autoComplete="name"
47
+ />
48
+
49
+ <InputError className="mt-2" message={errors.name} />
50
+ </div>
51
+
52
+ <div>
53
+ <InputLabel htmlFor="email" value="Email" />
54
+
55
+ <TextInput
56
+ id="email"
57
+ type="email"
58
+ className="mt-1 block w-full"
59
+ value={data.email}
60
+ onChange={(e) => setData('email', e.target.value)}
61
+ required
62
+ autoComplete="username"
63
+ />
64
+
65
+ <InputError className="mt-2" message={errors.email} />
66
+ </div>
67
+
68
+ <div className="flex items-center gap-4">
69
+ <PrimaryButton disabled={processing}>Save</PrimaryButton>
70
+
71
+ <Transition
72
+ show={recentlySuccessful}
73
+ enter="transition ease-in-out"
74
+ enterFrom="opacity-0"
75
+ leave="transition ease-in-out"
76
+ leaveTo="opacity-0"
77
+ >
78
+ <p className="text-sm text-gray-600">Saved.</p>
79
+ </Transition>
80
+ </div>
81
+ </form>
82
+ </section>
83
+ );
84
+ }
@@ -0,0 +1,66 @@
1
+ import { dashboard_path, login_path, register_path } from '@/routes';
2
+ import { PageProps } from '@/types';
3
+ import { Head, Link } from '@inertiajs/react';
4
+
5
+ export default function Welcome({ auth, railsVersion, rubyVersion }: PageProps<{ railsVersion: string, rubyVersion: string }>) {
6
+ return (
7
+ <>
8
+ <Head title="Welcome" />
9
+ <div className="flex flex-col items-center justify-center bg-[#F0E7E9] bg-center bg-cover text-[#261B23] not-italic font-normal leading-tight min-h-screen text-center" style={{
10
+ backgroundImage: 'url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEwMjQiIHZpZXdCb3g9IjAgMCAxNDQwIDEwMjQiIHdpZHRoPSIxNDQwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0xNDQwIDUxMC4wMDA2NDh2LTUxMC4wMDA2NDhoLTE0NDB2Mzg0LjAwMDY0OGM0MTcuMzExOTM5IDEzMS4xNDIxNzkgODkxIDE3MS41MTMgMTQ0MCAxMjZ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+)',
11
+ fontFamily: 'Sans-Serif',
12
+ fontSize: 'calc(0.9em + 0.5vw)',
13
+ }}>
14
+ <header className="absolute top-0 right-0 grid grid-cols-2 items-center gap-2 py-10 lg:grid-cols-3">
15
+ <div className="-mx-3 flex flex-1 justify-end">
16
+ {auth.user ? (
17
+ <Link
18
+ href={dashboard_path()}
19
+ className="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
20
+ >
21
+ Dashboard
22
+ </Link>
23
+ ) : (
24
+ <>
25
+ <Link
26
+ href={login_path()}
27
+ className="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
28
+ >
29
+ Log in
30
+ </Link>
31
+ <Link
32
+ href={register_path()}
33
+ className="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
34
+ >
35
+ Register
36
+ </Link>
37
+ </>
38
+ )}
39
+ </div>
40
+ </header>
41
+ <nav style={{
42
+ fontSize: 0,
43
+ height: '20vw',
44
+ lineHeight: 0,
45
+ maxHeight: '280px',
46
+ maxWidth: '280px',
47
+ minHeight: '86px',
48
+ minWidth: '86px',
49
+ width: '20vw',
50
+ }}>
51
+ <a className="bg-[#D30001] hover:bg-[#261B23] flex rounded-full" href="https://rubyonrails.org" target="_blank" style={{
52
+ transition: 'background 0.25s cubic-bezier(0.33, 1, 0.68, 1)',
53
+ filter: 'drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08))',
54
+ }}>
55
+ <img className="h-auto max-w-full w-full cursor-pointer" alt="" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjExMiIgdmlld0JveD0iMCAwIDExMiAxMTIiIHdpZHRoPSIxMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTEwMC4wODIzNTcgNDguOTk5NTQwM3Y0LjExMzUwMzRoLTcuMzAwMzM4OHYxLjg5ODU0aDMuNjg0MDcxN2MxLjk3MjUwOTEgMCA0LjA3MjUzNDEgMS40NjY0MzExIDQuMTk3OTk3MSAzLjk2NjUxMjRsLjAwNTkxMy4yMzczOTc3djEuNTgyMTE2N2MtLjA4NzgyNCAzLjAwNzk1OS0yLjU0MzEyMTEgNC4xMzkwMDE4LTQuMDcxNTM4OSA0LjIwMTE3NzNsLS4xMzIzNzEyLjAwMjczMjhoLTcuMzkwNzQ1MXYtNC4wOTA5MDE4bDcuNDgxMTUxOC0uMDIyNjAxNnYtMS45ODg5NDY3aC0zLjQ4MDY1NjdjLTEuNzc1MjU4MyAwLTQuMDgxODMyMS0xLjMzODkxNTMtNC4yMTk5OTQyLTMuOTU0OTIwMWwtLjAwNjUxNzYtLjI0ODk5di0xLjQyMzkwNWMwLTIuNjk4MjQwMiAyLjI3ODIxMjktNC4xODI4NTMgNC4wNjU0NjQtNC4yNjc4NDkxbC4xNjEwNDc4LS4wMDM4NjZ6bS0xOC42OTE1NzkgMHYxMS44NjU4NzUyaDYuMTcwMjU1MXY0LjEzNjEwNTFoLTEwLjczNTc5MTl2LTE2LjAwMTk4MDN6bS02LjQ0MTQ3NTEgMHYxNi4wMDE5ODAzaC00LjU4ODEzODV2LTE2LjAwMTk4MDN6bS0xNS4yMTA5MjIgMGg0LjQwNzMyNTFsLjE4NzcyNC4wMDY2ODEyYy4wMzM4NjA5LjAwMjI3MzUuMDY4OTQ1Ny4wMDUxNDk5LjEwNTE2MDEuMDA4NjY0M2wuMjMwMDg3NS4wMjkwMTc4YzEuMjkxMzgyOS4xOTg1NDc2IDMuNTQ2MzgxNiAxLjEyMTM4MTYgMy42NzUxNzA4IDMuOTA2ODU4N2wuMDA1NzY3Ny4yNTI2ODgxdjExLjc5ODA3MDJoLTQuMjcxNzE1MXYtMi44MjUyMDg0aC00LjEzNjEwNTF2Mi44MjUyMDg0aC00LjQwNzMyNTF2LTExLjc5ODA3MDJjMC0xLjMxODQzMDYgMS4wMDQwODI2LTQuMDQ2ODQ5NSAzLjk0Njg5OS00LjE5NzQxMXptLTE5LjczOTg1MTktLjAwMjc1ODEgOC41ODAxODU3LjAwMDU3NDkuMjM4OTIzNC4wMzY3NDYyLjE0Nzk0NTkuMDI5NDE3LjI3NDExNjEuMDY1MzU4Mi4yMTI1MTk4LjA1OTkwNjguMjMyMzIyNS4wNzQ3MTgyYy4wNDAxNzkzLjAxMzc3MDQuMDgxMDQwMi4wMjgyMjAxLjEyMjUxMS4wNDMzODA0bC4yNTU1NjkxLjA5OTczNzYuMjY2NzYxOC4xMTgyODUyYzEuMzU2MDUxNS42NDAyODY2IDMuMDAyNjg1NSAyLjAzMjE1ODMgMy4wMDI2ODU1IDUuMDE2Mjk0IDAgMy4xODMwNzgxLTEuNTQ2NTI0MSA0LjUwMTQ4OTktMi42OTg4MzYyIDUuMDQyMDQ3N2wtLjIxMDk2NTEuMDkyNTA0NS0uMTk4ODgyOC4wNzU4NjU1LS4xODM5NTc3LjA2MDgxODYtLjE2NjE4OTguMDQ3MzYzNi0uMjA5NzUwOC4wNDkzMDAxLS4yNTk2NDkzLjA0MTgzNjMgNS4wMDU2ODk1IDUuMDUwNTgzNmgtNi4zNzQ5NTg5bC0zLjcyNjIwODMtMy44NjA4OTA2djMuODYwODkwNmgtNC4zMDk4MzE0em0yMi4wMDcwNjAzLTMuMDg2OTE5My40NDQ5OTE3LjQyMjY4MDctLjI0Mjk2OC4xNzc5ODgxLS4zNDgyMzMxLjI2OTMzNjUtLjE5OTg1NzMuMTYyOTIwNGMtNS4yMzk4OTY3LTMuODc0NTcxNS05LjY0NzY0NTQtNS40MjIxNDA5LTEzLjE5NzExOS01LjgxNTM1MzdsLS41MjU5ODA3LS4wNTA0NzA4Yy0xLjAzOTA3NDItLjA4NDIxMjYtMi4wMDA3ODYxLS4wNjk4MTAxLTIuODg0NDMwMi4wMTE1NDYybC0uNDM1MzEzMi4wNDYxMTExYy0uMDcxNDY2OC4wMDg1NjYyLS4xNDIzOTA3LjAxNzU2MDgtLjIxMjc3MTIuMDI2OTY1M2wtLjQxNTc1OTUuMDYxMjAwNS0uNDAyNjk5MS4wNzAxNjA3LS4zODk2MTk0LjA3ODI0MTUtLjM3NjUxOTguMDg1NDQyN2MtLjA2MTY2MDYuMDE0NzkxNy0uMTIyNzc0Ni4wMjk4NDY4LS4xODMzNDE1LjA0NTE0N2wtLjM1NjgzMzkuMDk0NTk1Ny0uMzQzNjg1NC4wOTk1OTgyLS4zMzA1MTc0LjEwMzcyMTMtLjMxNzMyOTcuMTA2OTY0OS0uMzA0MTIyNS4xMDkzMjktLjI5MDg5NTYuMTEwODEzNy0uNDExNTAwMi4xNjcwODAzLS41MDIxOTAyLjIxOTc2MTUtLjY1MzM3MzYuMzA4ODk1Ni0uNjEwMTA1LjMwMDU2NzVjLTQuNjk4ODg2NiAyLjYyMTk3ODgtNi44NTM5Mjk0IDYuODQ2MDcxMS03Ljc2ODIwMDIgMTAuODg2NjQ2M2wtLjEyMjMxMDEuNTc1NzgzOGMtLjAzODA1MDQuMTkxMzgzMy0uMDczNDI3OC4zODIxNTc2LS4xMDYyNzMyLjU3MjEzMDFsLS4wOTEwODA0LjU2NzMxOTYtLjA3NjczMTguNTYxMzUyMS0uMDYzMjI3NS41NTQyMjc3Yy0uMDA5NDU5NC4wOTE3MTM0LS4wMTgzOTE0LjE4MzA4MTctLjAyNjgxMzQuMjc0MDgwOGwtLjA0NDU1NC41NDEzNzIyLS4wMzMxNjAxLjUzMTM1NTZjLS4wMDQ2MjQyLjA4NzY2MDMtLjAwODgwODcuMTc0ODU1MS0uMDEyNTcxMy4yNjE1NjAybC0uMDE3NjUyNS41MTQxNjE4LS4wMDgzNjkzLjUwMTI1MzEuMDAwMDY5Ni40ODcxODc2LjAxNDA4MDYuNzAxODc3OS4wMjgzMTk1LjY2MzcyMjkuMDM5NzA4OS42MjE2NjM3LjA0ODI0OTIuNTc1Njk5OS4wNTM5NDAyLjUyNTgzMTcuMDc1ODQ4MS42MTY3ODc5LjA3NDE0NjIuNTExOTM3LjA4MDAwMDIuNDc4Mzc1Ni4wNjg1OTk1LjM1NDA4NjNoLTE3Ljg1NTMxN2wuMDU4NDktLjQ0MTcyNTMuMDQ2MzM2OC0uMjk1MTQ0OC4wNjMwMjA2LS4zNjQ3MjQyLjExMzI4MTktLjU4OTIwNjkuMTA5Mzc4Ny0uNTE1MDkwNy4xODIwMjY1LS43Nzg1MDg4LjE2NjEzNzYtLjY0ODY3NzIuMTI1OTIzMy0uNDYxNTk0My4yMTMwNDc0LS43MzM4MjI1LjE1ODk2NTUtLjUxNTUwNDIuMTczMTcyOC0uNTM1NDY5OS4xODc5NDYzLS41NTQ1MjI2LjIwMzI4NTctLjU3MjY2Mi4yMTkxOTEzLS41ODk4ODgzLjIzNTY2MjgtLjYwNjIwMTQuMjUyNzAwNC0uNjIxNjAxMmMuMDQzNTY4LS4xMDQ4MzI4LjA4Nzg2OTQtLjIxMDI2OTEuMTMyOTE2Mi0uMzE2MjkwMWwuMjc5MzE4LS42NDI5ODg3LjI5Nzc3MDctLjY1NjEwNTYuMzE2Nzg5NC0uNjY4MzA5Mi4zMzYzNzQtLjY3OTU5OTcuMzU2NTI0OS0uNjg5OTc3Yy4zNjY3ODg5LS42OTQ4NjExLjc2NDY1MjktMS40MDM5MTg0IDEuMTk2MTM5My0yLjEyMzA2MjQgNC43NDYzNTAxLTcuOTEwNTgzNCAxMi44Mzc3NDY5LTEzLjkwMDAyNTIgMTkuNDE0ODMyLTE0LjQ4NzY2ODYgNS4wNDUzODA2LS41MDU0MDk0IDkuODkyNTQzNi45Mjc2ODIzIDEzLjk0NDM2MjggMi44Nzk2NDM1bC42NDk5ODU4LjMyMDg1ODIuNjM1NDc2OC4zMjg2MDkzLjYyMDQwMTkuMzM1MDE1Yy4xMDIxMTI2LjA1NjI5NDkuMjAzNTczNi4xMTI4MDA3LjMwNDM3MS4xNjk0ODkybC41OTY3Mjg2LjM0MjEwMTMuNTgwMjM5LjM0NTE0MzkuNTYzMTgzNi4zNDY4NDExLjU0NTU2MjQuMzQ3MTkzMi41MjczNzUzLjM0NjE5OTkuNTA4NjIyMy4zNDM4NjE0Yy4wODMxNzYyLjA1NzA0MDYuMTY1NTQ3NS4xMTM5Mjc3LjI0NzEwMi4xNzA2MzMzbC40Nzk0MzIuMzM3ODMxMi40NTkyNjQ0LjMzMjEyOTQuNDM4NTMxLjMyNTA4MjUuNDE3MjMxNy4zMTY2OTAyLjU4NDY3MzYuNDU2MzU2OS41MzM1Njc0LjQyOTkwNzEuNjI4NjIzNy41MjQyMTc5LjY0NjM0MzIuNTYxNDAxNXptLTMwLjcwMDEwNTYgMTQuNTcxMzI0MyAyLjQ0MDk4MDEuODgxNDY1Yy4xMTMwMDgzLjg4NTIzMTkuMjczMTAzNCAxLjcyMzM3NzEuNDQxMDQ2NCAyLjQ4ODI3NjFsLjEwMTM5MzYuNDQ5OTQwNi0yLjcxMjIwMDEtLjk3MTg3MTctLjAzMzgzNDctLjIxMjE2MTgtLjA2NjA0MjEtLjQ3NTU4NDMtLjA2MTE2MDEtLjU0MTIxOTVjLS4wNDgwMjg1LS40Nzc0NjAyLS4wODc1ODE0LTEuMDE5OTAwMy0uMTEwMTgzMS0xLjYxODg0NDR6bTMxLjUwNjcyMzktNy42NjE5NjUyaC0xLjUxNDMxMTdjLS45NDAwMjM4IDAtMS4yMzkxMjI0LjQwOTc3Mi0xLjMzNDI5MDEuNjcwNTM2bC0uMDI4MjI2Ni4wOTUwMzM5Yy0uMDAzMjM3LjAxNDA3My0uMDA1ODI2Ni4wMjcxNTI2LS4wMDc4OTgyLjAzOTA3MzJsLS4wMDgxNTczLjA3MTUyMzktLjAwMDEyOTUgMy45MTUzODY0aDQuMTM2MTA1MWwtLjAwMTg1MzMtMy45MzQ3Njk0LS4wMTAyNjEyLS4wNjY5OTEzLS4wMjU3MjA3LS4wOTg3MjQxYy0uMDgzNTM1Ny0uMjU5MTUwNy0uMzUwOTEzNS0uNjkxMDY4Ni0xLjIwNTI1NjUtLjY5MTA2ODZ6bS01MC40OTIxMjQyLjMzOTAyNSAyLjU5OTE5MTcuOTQ5MjctLjQwODY0MS45NTE1ODM5Yy0uMjEyNDg4Mi40OTk0NjQ0LS40Mjc3Nzk2IDEuMDE0ODE0Mi0uNjAzMzU3NSAxLjQ1Nzc0NWwtLjExODA4NDkuMzAyODcxMi0yLjU5OTE5MTctLjk0OTI3LjEzMjM2NjItLjM0NTMwMzMuMjU4Mzg4OS0uNjM5MjAzNC4zMDU3MTExLS43MjgxOTc0LjMyNTQ4NzUtLjc1MzYyNDRjLjAzNjI0MzgtLjA4Mjc5NDMuMDcyMzQzNy0uMTY0ODgyMy4xMDgxMjk3LS4yNDU4NzE2em0zNS40NDUxMjA5LS4xNDM0NDQ5aC0zLjQ1Njg0Mzl2My42NTg4NjczaDMuNDM0Mzk2OGwuMDU0NzEwNi0uMDI1MzkyLjA4NjU5ODQtLjA0ODg5ODMuMTE0NzUzNi0uMDc4NjgyMmMuMjkyNjQyOC0uMjIxMTQ0OC43MzE2MDcxLS43MTQ5Nzk3LjczMTYwNzEtMS42ODc2ODQ3cy0uNDI4OTg3OC0xLjQ1NjU2MzQtLjcxNDk3OTctMS42NzEwNTczbC0uMTEyMTQ1NS0uMDc2MDc0MS0uMDg0NjMwMy0uMDQ2OTMwMXptLTE1LjQ0MjY0NTYtLjc2MDYyMTggMS42MjczMjAxIDEuMjg4Mjk1MWMtLjE4MDgxMzQuNzA1MTcyLS4zMTgyMzE1IDEuNDEwMzQ0LS40MTIyNTQ1IDIuMTE1NTE2bC0uMDYyMzgwNi41Mjg4NzktMS44MzA3MzUtMS40NDY1MDY3Yy4xODA4MTM0LS44MTM2Ni4zODQyMjg0LTEuNjQ5OTIxNy42NzgwNS0yLjQ4NjE4MzR6bTQuMDAwNDk1MS02LjMwNTg2NTEgMS4wMTcwNzUgMS41MzY5MTM0Yy0uMzk3Nzg5My40MTU4NzA3LS43NjY2NDg1LjgzMTc0MTMtMS4wOTUwMDU1IDEuMjcwNzU2MWwtLjIzODQ5MjguMzMzOTYyMy0xLjA4NDg4MDEtMS42MjczMjAxYy40MDY4My0uNTE5ODM4My44ODE0NjUxLTEuMDM5Njc2NyAxLjQwMTMwMzQtMS41MTQzMTE3em0tMTYuMTgyNzkzNi0zLjM0NTA0NjcgMS42MDQ3MTgzIDEuNDAxMzAzNGMtLjQwNjgzLjQyMzc4MTItLjgwMDk0NjUuODcyOTg5NC0xLjE3MjgxNDYgMS4zMjg1NTQybC0uMzY0MDk4Ny40NTY5Nzc1LTEuNzQwMzI4NC0xLjQ5MTcxMDFjLjUxOTgzODMtLjU2NTA0MTYgMS4wODQ4OC0xLjEzMDA4MzMgMS42NzI1MjM0LTEuNjk1MTI1em0yMi4zOTgyNTIxLS4wOTA0MDY3LjQ5NzIzNjYgMS40OTE3MTAxYy0uNTI0MzU4Ni4xNjI3MzItMS4wNDg3MTczLjM2ODg1OTItMS41NzMwNzYuNjA2ODA5NWwtLjM5MzI2OS4xODQyNDg4LS41MTk4Mzg0LTEuNTU5NTE1Yy41NjUwNDE3LS4yNDg2MTg0IDEuMjIwNDkwMS0uNDk3MjM2NyAxLjk4ODk0NjgtLjcyMzI1MzR6bTUuMjg4NzktLjU0MjQ0Yy41Nzg2MDI3LjAzNjE2MjcgMS4xNzE2NzA1LjEwMTI1NTUgMS43NzkyMDMzLjIwNjg1MDVsLjQ1ODM2MTguMDg2OTcxMi0uMDkwNDA2NyAxLjQwMTMwMzRjLS41OTY2ODQtLjEyNjU2OTQtMS4xOTMzNjgtLjIwOTc0MzUtMS43OTAwNTItLjI0OTUyMjRsLS40NDc1MTMtLjAyMTY5NzZ6bS0xOC41NTU5Njg2LTYuMjM4MDYwMSAxLjAxNzA3NSAxLjU1OTUxNWMtLjQ0MDczMjUuMjIwMzY2My0uODY4NzUxNi40NjYxNTk0LTEuMzAzMTI3NC43Mjc4NDQzbC0uNDM3MjAxLjI2NjYyOTEtMS4wMzk2NzY3LTEuNTgyMTE2N2MuNjEwMjQ1LS4zNjE2MjY3IDEuMTk3ODg4NC0uNjc4MDUgMS43NjI5MzAxLS45NzE4NzE3em0xOC41MTA3NjUzLjYzMjg0NjcuMDkwNDA2Ny0xLjQ5MTcxLjQ0MzUwNzMuMTMwNTU2NC4zODM5ODAzLjEyMDMyNTIuMzI4NTQ1OC4xMDk5MDc5LjI3NzIwMzcuMDk5MzA0OC4zMjg0OTE1LjEyODY2OS4yOTY0ODguMTMyNzExMi4xMzQxNDUxLjA2OTU4MzgtLjA5MDQwNjcgMS41MTQzMTE3Yy0uNDgyMTY4OS0uMTk1ODgxMS0uOTY0MzM3OC0uMzgxNzE3LTEuNDUzMjAzNS0uNTU3NTA3OHptLTguNTQzNDMwMS0yLjgyNTIwODQuNDUyMDMzMyAxLjM3ODcwMTdoLS4yMjYwMTY3Yy0uNDkxNTg2MiAwLS45ODMxNzI1LjAxMjcxMzQtMS40NzQ3NTg4LjA0NzY3NTRsLS40OTE1ODYyLjA0MjczMTMtLjQyOTQzMTctMS4zMzM0OTg0Yy43NDU4NTUtLjA5MDQwNjcgMS40NjkxMDg0LS4xMzU2MSAyLjE2OTc2MDEtLjEzNTYxeiIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==" />
56
+ </a>
57
+ </nav>
58
+
59
+ <ul className="bottom-0 left-0 list-none mx-8 mt-0 mb-8 right-0 absolute">
60
+ <li><strong>Rails version:</strong> {railsVersion}</li>
61
+ <li><strong>Ruby version:</strong> {rubyVersion}</li>
62
+ </ul>
63
+ </div>
64
+ </>
65
+ );
66
+ }
@@ -0,0 +1,34 @@
1
+ import './bootstrap'
2
+ import '../../assets/builds/tailwind.css'
3
+
4
+ import { createRoot } from 'react-dom/client';
5
+ import { createInertiaApp } from '@inertiajs/react';
6
+
7
+ async function resolvePageComponent<T>(path: string|string[], pages: Record<string, Promise<T> | (() => Promise<T>)>): Promise<T> {
8
+ for (const p of (Array.isArray(path) ? path : [path])) {
9
+ const page = pages[p]
10
+
11
+ if (typeof page === 'undefined') {
12
+ continue
13
+ }
14
+
15
+ return typeof page === 'function' ? page() : page
16
+ }
17
+
18
+ throw new Error(`Page not found: ${path}`)
19
+ }
20
+
21
+ const appName = import.meta.env.VITE_APP_NAME || 'Rails';
22
+
23
+ createInertiaApp({
24
+ title: (title) => `${title} - ${appName}`,
25
+ resolve: (name) => resolvePageComponent(`../Pages/${name}.tsx`, import.meta.glob('../Pages/**/*.tsx')),
26
+ setup({ el, App, props }) {
27
+ const root = createRoot(el);
28
+
29
+ root.render(<App {...props} />);
30
+ },
31
+ progress: {
32
+ color: '#4B5563',
33
+ },
34
+ });