kaze 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +42 -0
- data/bin/kaze +11 -0
- data/lib/kaze/commands/install_command.rb +63 -0
- data/lib/kaze/commands/install_inertia_stacks.rb +151 -0
- data/lib/kaze/commands.rb +2 -0
- data/lib/kaze/version.rb +3 -0
- data/lib/kaze.rb +9 -0
- data/stubs/default/Procfile.dev +3 -0
- data/stubs/default/app/assets/stylesheets/application.css +1 -0
- data/stubs/default/app/assets/stylesheets/application.tailwind.css +3 -0
- data/stubs/default/app/controllers/application_controller.rb +5 -0
- data/stubs/default/app/controllers/auth/authenticated_session_controller.rb +28 -0
- data/stubs/default/app/controllers/auth/new_password_controller.rb +18 -0
- data/stubs/default/app/controllers/auth/password_reset_link_controller.rb +17 -0
- data/stubs/default/app/controllers/auth/registered_user_controller.rb +19 -0
- data/stubs/default/app/controllers/concerns/authenticate.rb +34 -0
- data/stubs/default/app/controllers/concerns/handle_inertia_requests.rb +9 -0
- data/stubs/default/app/controllers/concerns/verify_csrf_token.rb +24 -0
- data/stubs/default/app/controllers/dashboard_controller.rb +5 -0
- data/stubs/default/app/controllers/password_controller.rb +11 -0
- data/stubs/default/app/controllers/profile_controller.rb +31 -0
- data/stubs/default/app/controllers/welcome_controller.rb +10 -0
- data/stubs/default/app/forms/application_form.rb +9 -0
- data/stubs/default/app/forms/auth/login_form.rb +18 -0
- data/stubs/default/app/forms/auth/new_password_form.rb +21 -0
- data/stubs/default/app/forms/auth/register_form.rb +7 -0
- data/stubs/default/app/forms/auth/send_password_reset_link_form.rb +22 -0
- data/stubs/default/app/forms/delete_user_form.rb +5 -0
- data/stubs/default/app/forms/update_password_form.rb +6 -0
- data/stubs/default/app/forms/update_profile_information_form.rb +6 -0
- data/stubs/default/app/mailers/application_mailer.rb +11 -0
- data/stubs/default/app/mailers/user_mailer.rb +8 -0
- data/stubs/default/app/models/application_record.rb +3 -0
- data/stubs/default/app/models/concerns/can_reset_password.rb +5 -0
- data/stubs/default/app/models/current.rb +3 -0
- data/stubs/default/app/models/user.rb +11 -0
- data/stubs/default/app/validators/current_password_validator.rb +5 -0
- data/stubs/default/app/validators/email_validator.rb +7 -0
- data/stubs/default/app/validators/lowercase_validator.rb +5 -0
- data/stubs/default/app/validators/uniqueness_validator.rb +24 -0
- data/stubs/default/app/views/layouts/mailer.html.erb +374 -0
- data/stubs/default/app/views/layouts/mailer.text.erb +11 -0
- data/stubs/default/app/views/user_mailer/reset_password.html.erb +39 -0
- data/stubs/default/bin/dev +16 -0
- data/stubs/default/bin/vite +27 -0
- data/stubs/default/config/routes.rb +27 -0
- data/stubs/default/config/vite.json +16 -0
- data/stubs/default/db/migrate/20240101000000_create_users.rb +14 -0
- data/stubs/default/db/migrate/20240101000001_create_delayed_jobs.rb +22 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ApplicationLogo.tsx +12 -0
- data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +14 -0
- data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +17 -0
- data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +99 -0
- data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +9 -0
- data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +9 -0
- data/stubs/inertia-react-ts/app/javascript/Components/Modal.tsx +68 -0
- data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +18 -0
- data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +17 -0
- data/stubs/inertia-react-ts/app/javascript/Components/ResponsiveNavLink.tsx +16 -0
- data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +18 -0
- data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +30 -0
- data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +131 -0
- data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +19 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +52 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +98 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +118 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +74 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +22 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +33 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +100 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +114 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +84 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +66 -0
- data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +34 -0
- data/stubs/inertia-react-ts/app/javascript/entrypoints/bootstrap.ts +4 -0
- data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +7 -0
- data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +12 -0
- data/stubs/inertia-react-ts/app/javascript/types/vite-env.d.ts +1 -0
- data/stubs/inertia-react-ts/app/views/layouts/application.html.erb +26 -0
- data/stubs/inertia-react-ts/config/tailwind.config.js +22 -0
- data/stubs/inertia-react-ts/package.json +26 -0
- data/stubs/inertia-react-ts/tsconfig.json +19 -0
- data/stubs/inertia-react-ts/vite.config.ts +13 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +8 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/Checkbox.vue +29 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/DangerButton.vue +7 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +75 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/DropdownLink.vue +16 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/InputError.vue +13 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/InputLabel.vue +12 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +96 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/NavLink.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/PrimaryButton.vue +7 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/ResponsiveNavLink.vue +21 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/SecondaryButton.vue +19 -0
- data/stubs/inertia-vue-ts/app/javascript/Components/TextInput.vue +23 -0
- data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +155 -0
- data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +20 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +60 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +93 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +106 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +89 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +22 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +42 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +98 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +108 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +78 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +56 -0
- data/stubs/inertia-vue-ts/app/javascript/entrypoints/application.ts +34 -0
- data/stubs/inertia-vue-ts/app/javascript/entrypoints/bootstrap.ts +4 -0
- data/stubs/inertia-vue-ts/app/javascript/types/global.d.ts +13 -0
- data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +12 -0
- data/stubs/inertia-vue-ts/app/javascript/types/vite-env.d.ts +1 -0
- data/stubs/inertia-vue-ts/app/views/layouts/application.html.erb +25 -0
- data/stubs/inertia-vue-ts/config/tailwind.config.js +22 -0
- data/stubs/inertia-vue-ts/package.json +24 -0
- data/stubs/inertia-vue-ts/tsconfig.json +19 -0
- data/stubs/inertia-vue-ts/vite.config.ts +13 -0
- 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
|
+
}
|
data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx
ADDED
@@ -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
|
+
});
|