massive improvements to tests, streamlined and synced migrations, fixed a lot of wrong or old table field references. implemented a lot of pages in react for website frontend

This commit is contained in:
Codex Agent
2025-09-30 21:09:52 +02:00
parent 21c9391e2c
commit d1733686a6
114 changed files with 2867 additions and 2411 deletions

View File

@@ -4,7 +4,7 @@ import { RouterProvider } from 'react-router-dom';
import { AuthProvider } from './auth/context';
import { router } from './router';
import '../../css/app.css';
import { initializeTheme } from '@/hooks/use-appearance';
import { initializeTheme } from '@/hooks/use-appearance.tsx';
initializeTheme();
const rootEl = document.getElementById('root')!;

View File

@@ -1,4 +1,4 @@
import ProfileController from '@/actions/App/Http/Controllers/Settings/ProfileController';
import { destroy } from '@/actions/App/Http/Controllers/Settings/ProfileController';
import HeadingSmall from '@/components/heading-small';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';

View File

@@ -2,7 +2,7 @@ import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSep
import { UserInfo } from '@/components/user-info';
import { useMobileNavigation } from '@/hooks/use-mobile-navigation';
import { logout } from '@/routes';
import { edit } from '@/routes/profile';
import { edit } from '@/routes/settings/profile';
import { type User } from '@/types';
import { Link, router } from '@inertiajs/react';
import { LogOut, Settings } from 'lucide-react';

View File

@@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import '../../css/app.css';
import { initializeTheme } from '@/hooks/use-appearance';
import { initializeTheme } from '@/hooks/use-appearance.tsx';
import { ToastProvider } from './components/ToastHost';
initializeTheme();

View File

@@ -4,7 +4,7 @@ import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import { appearance } from '@/routes';
import { edit as editPassword } from '@/routes/password';
import { edit } from '@/routes/profile';
import { edit } from '@/routes/settings/profile';
import { type NavItem } from '@/types';
import { Link } from '@inertiajs/react';
import { type PropsWithChildren } from 'react';

View File

@@ -1,4 +1,4 @@
import ConfirmablePasswordController from '@/actions/App/Http/Controllers/Auth/ConfirmablePasswordController';
import { store } from '@/actions/App/Http/Controllers/Auth/ConfirmablePasswordController';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -15,7 +15,7 @@ export default function ConfirmPassword() {
>
<Head title="Confirm password" />
<Form {...ConfirmablePasswordController.store.form()} resetOnSuccess={['password']}>
<Form {...store.form()} resetOnSuccess={['password']}>
{({ processing, errors }) => (
<div className="space-y-6">
<div className="grid gap-2">

View File

@@ -1,5 +1,5 @@
// Components
import PasswordResetLinkController from '@/actions/App/Http/Controllers/Auth/PasswordResetLinkController';
import { store } from '@/actions/App/Http/Controllers/Auth/PasswordResetLinkController';
import { login } from '@/routes';
import { Form, Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
@@ -19,7 +19,7 @@ export default function ForgotPassword({ status }: { status?: string }) {
{status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
<div className="space-y-6">
<Form {...PasswordResetLinkController.store.form()}>
<Form {...store.form()}>
{({ processing, errors }) => (
<>
<div className="grid gap-2">

View File

@@ -1,4 +1,4 @@
import AuthenticatedSessionController from '@/actions/App/Http/Controllers/Auth/AuthenticatedSessionController';
import { useForm, router } from '@inertiajs/react';
import InputError from '@/components/input-error';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
@@ -8,7 +8,7 @@ import { Label } from '@/components/ui/label';
import AuthLayout from '@/layouts/auth-layout';
import { register } from '@/routes';
import { request } from '@/routes/password';
import { Form, Head } from '@inertiajs/react';
import { Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
interface LoginProps {
@@ -17,70 +17,87 @@ interface LoginProps {
}
export default function Login({ status, canResetPassword }: LoginProps) {
const { data, setData, post, processing, errors } = useForm({
email: '',
password: '',
remember: false,
});
const submit = (e: React.FormEvent) => {
e.preventDefault();
post('/login');
};
return (
<AuthLayout title="Log in to your account" description="Enter your email and password below to log in">
<Head title="Log in" />
<Form {...AuthenticatedSessionController.store.form()} resetOnSuccess={['password']} className="flex flex-col gap-6">
{({ processing, errors }) => (
<>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
name="email"
required
autoFocus
tabIndex={1}
autoComplete="email"
placeholder="email@example.com"
/>
<InputError message={errors.email} />
</div>
<form onSubmit={submit} className="flex flex-col gap-6">
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
name="email"
required
autoFocus
tabIndex={1}
autoComplete="email"
placeholder="email@example.com"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
/>
<InputError message={errors.email} />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
{canResetPassword && (
<TextLink href={request()} className="ml-auto text-sm" tabIndex={5}>
Forgot password?
</TextLink>
)}
</div>
<Input
id="password"
type="password"
name="password"
required
tabIndex={2}
autoComplete="current-password"
placeholder="Password"
/>
<InputError message={errors.password} />
</div>
<div className="flex items-center space-x-3">
<Checkbox id="remember" name="remember" tabIndex={3} />
<Label htmlFor="remember">Remember me</Label>
</div>
<Button type="submit" className="mt-4 w-full" tabIndex={4} disabled={processing}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
Log in
</Button>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
{canResetPassword && (
<TextLink href={request()} className="ml-auto text-sm" tabIndex={5}>
Forgot password?
</TextLink>
)}
</div>
<Input
id="password"
type="password"
name="password"
required
tabIndex={2}
autoComplete="current-password"
placeholder="Password"
value={data.password}
onChange={(e) => setData('password', e.target.value)}
/>
<InputError message={errors.password} />
</div>
<div className="text-center text-sm text-muted-foreground">
Don't have an account?{' '}
<TextLink href={register()} tabIndex={5}>
Sign up
</TextLink>
</div>
</>
)}
</Form>
<div className="flex items-center space-x-3">
<Checkbox
id="remember"
name="remember"
tabIndex={3}
checked={data.remember}
onCheckedChange={(checked) => setData('remember', Boolean(checked))}
/>
<Label htmlFor="remember">Remember me</Label>
</div>
<Button type="submit" className="mt-4 w-full" tabIndex={4} disabled={processing}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
Log in
</Button>
</div>
<div className="text-center text-sm text-muted-foreground">
Don't have an account?{' '}
<TextLink href={register()} tabIndex={5}>
Sign up
</TextLink>
</div>
</form>
{status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
</AuthLayout>

View File

@@ -1,100 +1,251 @@
import RegisteredUserController from '@/actions/App/Http/Controllers/Auth/RegisteredUserController';
import { login } from '@/routes';
import { Form, Head } from '@inertiajs/react';
import React from 'react';
import { useForm, router } from '@inertiajs/react';
import { Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import InputError from '@/components/input-error';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import AuthLayout from '@/layouts/auth-layout';
interface RegisterProps {
package?: {
id: number;
name: string;
description: string;
price: number;
} | null;
}
export default function Register({ package: initialPackage }: RegisterProps) {
const { data, setData, post, processing, errors } = useForm({
name: '',
username: '',
email: '',
password: '',
password_confirmation: '',
first_name: '',
last_name: '',
address: '',
phone: '',
privacy_consent: false,
package_id: initialPackage?.id || null,
});
const submit = (e: React.FormEvent) => {
e.preventDefault();
router.post('/register');
};
export default function Register() {
return (
<AuthLayout title="Create an account" description="Enter your details below to create your account">
<Head title="Register" />
<Form
{...RegisteredUserController.store.form()}
resetOnSuccess={['password', 'password_confirmation']}
disableWhileProcessing
className="flex flex-col gap-6"
>
{({ processing, errors }) => (
<>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
type="text"
required
autoFocus
tabIndex={1}
autoComplete="name"
name="name"
placeholder="Full name"
/>
<InputError message={errors.name} className="mt-2" />
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
required
tabIndex={2}
autoComplete="email"
name="email"
placeholder="email@example.com"
/>
<InputError message={errors.email} />
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
required
tabIndex={3}
autoComplete="new-password"
name="password"
placeholder="Password"
/>
<InputError message={errors.password} />
</div>
<div className="grid gap-2">
<Label htmlFor="password_confirmation">Confirm password</Label>
<Input
id="password_confirmation"
type="password"
required
tabIndex={4}
autoComplete="new-password"
name="password_confirmation"
placeholder="Confirm password"
/>
<InputError message={errors.password_confirmation} />
</div>
<Button type="submit" className="mt-2 w-full" tabIndex={5}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
Create account
</Button>
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<Head title="Registrieren" />
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Registrieren
</h2>
{initialPackage && (
<div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-md">
<h3 className="text-lg font-semibold text-blue-900 mb-2">{initialPackage.name}</h3>
<p className="text-blue-800 mb-2">{initialPackage.description}</p>
<p className="text-sm text-blue-700">
{initialPackage.price === 0 ? 'Kostenlos' : `${initialPackage.price}`}
</p>
</div>
)}
</div>
<form onSubmit={submit} className="mt-8 space-y-6">
<div className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Name
</label>
<input
id="name"
name="name"
type="text"
required
autoFocus
value={data.name}
onChange={(e) => setData('name', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Vollständiger Name"
/>
{errors.name && <p className="mt-2 text-sm text-red-600">{errors.name}</p>}
</div>
<div className="text-center text-sm text-muted-foreground">
Already have an account?{' '}
<TextLink href={login()} tabIndex={6}>
Log in
</TextLink>
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
Benutzername
</label>
<input
id="username"
name="username"
type="text"
required
value={data.username}
onChange={(e) => setData('username', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Benutzername"
/>
{errors.username && <p className="text-sm text-red-600">{errors.username}</p>}
</div>
</>
)}
</Form>
</AuthLayout>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
E-Mail-Adresse
</label>
<input
id="email"
name="email"
type="email"
required
value={data.email}
onChange={(e) => setData('email', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="email@example.com"
/>
{errors.email && <p className="text-sm text-red-600">{errors.email}</p>}
</div>
<div>
<label htmlFor="first_name" className="block text-sm font-medium text-gray-700">
Vorname
</label>
<input
id="first_name"
name="first_name"
type="text"
required
value={data.first_name}
onChange={(e) => setData('first_name', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Vorname"
/>
{errors.first_name && <p className="text-sm text-red-600">{errors.first_name}</p>}
</div>
<div>
<label htmlFor="last_name" className="block text-sm font-medium text-gray-700">
Nachname
</label>
<input
id="last_name"
name="last_name"
type="text"
required
value={data.last_name}
onChange={(e) => setData('last_name', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Nachname"
/>
{errors.last_name && <p className="text-sm text-red-600">{errors.last_name}</p>}
</div>
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
Adresse
</label>
<textarea
id="address"
name="address"
required
value={data.address}
onChange={(e) => setData('address', e.target.value)}
rows={3}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Adresse"
/>
{errors.address && <p className="text-sm text-red-600">{errors.address}</p>}
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700">
Telefon
</label>
<input
id="phone"
name="phone"
type="tel"
required
value={data.phone}
onChange={(e) => setData('phone', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Telefonnummer"
/>
{errors.phone && <p className="text-sm text-red-600">{errors.phone}</p>}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Passwort
</label>
<input
id="password"
name="password"
type="password"
required
value={data.password}
onChange={(e) => setData('password', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Passwort"
/>
{errors.password && <p className="text-sm text-red-600">{errors.password}</p>}
</div>
<div>
<label htmlFor="password_confirmation" className="block text-sm font-medium text-gray-700">
Passwort bestätigen
</label>
<input
id="password_confirmation"
name="password_confirmation"
type="password"
required
value={data.password_confirmation}
onChange={(e) => setData('password_confirmation', e.target.value)}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Passwort bestätigen"
/>
{errors.password_confirmation && <p className="text-sm text-red-600">{errors.password_confirmation}</p>}
</div>
<div className="flex items-start">
<input
id="privacy_consent"
name="privacy_consent"
type="checkbox"
required
checked={data.privacy_consent}
onChange={(e) => setData('privacy_consent', e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="privacy_consent" className="ml-2 block text-sm text-gray-900">
Ich stimme der{' '}
<a href="/de/datenschutz" className="text-blue-600 hover:underline">
Datenschutzerklärung
</a>{' '}
zu.
</label>
{errors.privacy_consent && <p className="mt-2 text-sm text-red-600">{errors.privacy_consent}</p>}
</div>
</div>
<button
type="submit"
disabled={processing}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-300 disabled:opacity-50"
>
{processing && <LoaderCircle className="h-4 w-4 animate-spin mr-2" />}
Account erstellen
</button>
<div className="text-center">
<p className="text-sm text-gray-600">
Bereits registriert?{' '}
<a href="/login" className="font-medium text-blue-600 hover:text-blue-500">
Anmelden
</a>
</p>
</div>
</form>
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import NewPasswordController from '@/actions/App/Http/Controllers/Auth/NewPasswordController';
import { store } from '@/actions/App/Http/Controllers/Auth/NewPasswordController';
import { Form, Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';

View File

@@ -1,5 +1,5 @@
// Components
import EmailVerificationNotificationController from '@/actions/App/Http/Controllers/Auth/EmailVerificationNotificationController';
import { store } from '@/actions/App/Http/Controllers/Auth/EmailVerificationNotificationController';
import { logout } from '@/routes';
import { Form, Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
@@ -19,7 +19,7 @@ export default function VerifyEmail({ status }: { status?: string }) {
</div>
)}
<Form {...EmailVerificationNotificationController.store.form()} className="space-y-6 text-center">
<Form {...store.form()} className="space-y-6 text-center">
{({ processing }) => (
<>
<Button disabled={processing} variant="secondary">

View File

@@ -1,4 +1,4 @@
import PasswordController from '@/actions/App/Http/Controllers/Settings/PasswordController';
import { store } from '@/actions/App/Http/Controllers/Settings/PasswordController';
import InputError from '@/components/input-error';
import AppLayout from '@/layouts/app-layout';
import SettingsLayout from '@/layouts/settings/layout';

View File

@@ -1,4 +1,4 @@
import ProfileController from '@/actions/App/Http/Controllers/Settings/ProfileController';
import { store } from '@/actions/App/Http/Controllers/Settings/ProfileController';
import { send } from '@/routes/verification';
import { type BreadcrumbItem, type SharedData } from '@/types';
import { Transition } from '@headlessui/react';
@@ -12,7 +12,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import AppLayout from '@/layouts/app-layout';
import SettingsLayout from '@/layouts/settings/layout';
import { edit } from '@/routes/profile';
import { edit } from '@/routes/settings/profile';
const breadcrumbs: BreadcrumbItem[] = [
{