feat: Implementierung des Checkout-Logins mit E-Mail/Username-Support

This commit is contained in:
Codex Agent
2025-10-08 21:57:46 +02:00
parent cee279cbab
commit 417b1da484
25 changed files with 730 additions and 212 deletions

View File

@@ -43,7 +43,7 @@ export default function LoginForm({ onSuccess, canResetPassword = true, locale }
const loginEndpoint = '/checkout/login';
const [values, setValues] = useState({
email: "",
identifier: "",
password: "",
remember: false,
});
@@ -98,7 +98,7 @@ export default function LoginForm({ onSuccess, canResetPassword = true, locale }
},
credentials: "same-origin",
body: JSON.stringify({
email: values.email,
identifier: values.identifier,
password: values.password,
remember: values.remember,
locale: resolvedLocale,
@@ -146,20 +146,20 @@ export default function LoginForm({ onSuccess, canResetPassword = true, locale }
<form onSubmit={submit} className="flex flex-col gap-6" noValidate>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="email">{t("login.email")}</Label>
<Label htmlFor="identifier">{t("login.identifier") || t("login.email")}</Label>
<Input
id="email"
type="email"
name="email"
id="identifier"
type="text"
name="identifier"
required
autoFocus
placeholder={t("login.email_placeholder")}
value={values.email}
onChange={(event) => updateValue("email", event.target.value)}
placeholder={t("login.identifier_placeholder") || t("login.email_placeholder")}
value={values.identifier}
onChange={(event) => updateValue("identifier", event.target.value)}
/>
<InputError message={errors.email} />
<InputError message={errors.identifier || errors.email} />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">{t("login.password")}</Label>
@@ -180,7 +180,7 @@ export default function LoginForm({ onSuccess, canResetPassword = true, locale }
/>
<InputError message={errors.password} />
</div>
<div className="flex items-center space-x-3">
<Checkbox
id="remember"
@@ -190,7 +190,7 @@ export default function LoginForm({ onSuccess, canResetPassword = true, locale }
/>
<Label htmlFor="remember">{t("login.remember")}</Label>
</div>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting && <LoaderCircle className="h-4 w-4 animate-spin mr-2" />}
{t("login.submit")}

View File

@@ -6,21 +6,23 @@ import { Label } from '@/components/ui/label';
import AuthLayout from '@/layouts/auth-layout';
import { Form, Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { useTranslation } from 'react-i18next';
export default function ConfirmPassword() {
return (
<AuthLayout
title="Confirm your password"
description="This is a secure area of the application. Please confirm your password before continuing."
>
<Head title="Confirm password" />
const { t } = useTranslation('auth');
return (
<AuthLayout
title={t('auth.confirm.title', 'Confirm your password')}
description={t('auth.confirm.description', 'This is a secure area of the application. Please confirm your password before continuing.')}
>
<Head title={t('auth.confirm.title', 'Confirm password')} />
<Form {...store.form()} resetOnSuccess={['password']}>
{({ processing, errors }) => (
<div className="space-y-6">
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" name="password" placeholder="Password" autoComplete="current-password" autoFocus />
<Label htmlFor="password">{t('auth.confirm.password', 'Password')}</Label>
<Input id="password" type="password" name="password" placeholder={t('auth.confirm.password_placeholder')} autoComplete="current-password" autoFocus />
<InputError message={errors.password} />
</div>
@@ -28,7 +30,7 @@ export default function ConfirmPassword() {
<div className="flex items-center">
<Button className="w-full" disabled={processing}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
Confirm password
{t('auth.confirm.submit', 'Confirm password')}
</Button>
</div>
</div>

View File

@@ -12,9 +12,10 @@ import { Label } from '@/components/ui/label';
import AuthLayout from '@/layouts/auth-layout';
export default function ForgotPassword({ status }: { status?: string }) {
return (
<AuthLayout title="Forgot password" description="Enter your email to receive a password reset link">
<Head title="Forgot password" />
const { t } = useTranslation('auth');
return (
<AuthLayout title={t('auth.forgot.title', 'Forgot password')} description={t('auth.forgot.description', 'Enter your email to receive a password reset link')}>
<Head title={t('auth.forgot.title', 'Forgot password')} />
{status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
@@ -24,7 +25,7 @@ export default function ForgotPassword({ status }: { status?: string }) {
<>
<div className="grid gap-2">
<Label htmlFor="email">Email address</Label>
<Input id="email" type="email" name="email" autoComplete="off" autoFocus placeholder="email@example.com" />
<Input id="email" type="email" name="email" autoComplete="off" autoFocus placeholder={t('auth.forgot.email_placeholder')} />
<InputError message={errors.email} />
</div>
@@ -41,7 +42,7 @@ export default function ForgotPassword({ status }: { status?: string }) {
<div className="space-x-1 text-center text-sm text-muted-foreground">
<span>Or, return to</span>
<TextLink href={login()}>log in</TextLink>
<TextLink href={login()}>{t('auth.forgot.back')}</TextLink>
</div>
</div>
</AuthLayout>

View File

@@ -1,6 +1,7 @@
import { store } from '@/actions/App/Http/Controllers/Auth/NewPasswordController';
import { Form, Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
@@ -14,25 +15,26 @@ interface ResetPasswordProps {
}
export default function ResetPassword({ token, email }: ResetPasswordProps) {
const { t } = useTranslation('auth');
return (
<AuthLayout title="Reset password" description="Please enter your new password below">
<Head title="Reset password" />
<AuthLayout title={t('auth.reset.title', 'Reset password')} description={t('auth.reset.description', 'Please enter your new password below')}>
<Head title={t('auth.reset.title', 'Reset password')} />
<Form
{...NewPasswordController.store.form()}
{...store.form()}
transform={(data) => ({ ...data, token, email })}
resetOnSuccess={['password', 'password_confirmation']}
>
{({ processing, errors }) => (
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" name="email" autoComplete="email" value={email} className="mt-1 block w-full" readOnly />
<InputError message={errors.email} className="mt-2" />
<Label htmlFor="email">{t('auth.reset.email')}</Label>
<Input id="email" type="email" name="email" autoComplete="email" value={email} className="mt-1 block w-full" readOnly />
<InputError message={errors.email} className="mt-2" />
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Label htmlFor="password">{t('auth.reset.password', 'Password')}</Label>
<Input
id="password"
type="password"
@@ -40,27 +42,27 @@ export default function ResetPassword({ token, email }: ResetPasswordProps) {
autoComplete="new-password"
className="mt-1 block w-full"
autoFocus
placeholder="Password"
placeholder={t('auth.reset.password_placeholder')}
/>
<InputError message={errors.password} />
</div>
<div className="grid gap-2">
<Label htmlFor="password_confirmation">Confirm password</Label>
<Label htmlFor="password_confirmation">{t('auth.reset.confirm_password', 'Confirm password')}</Label>
<Input
id="password_confirmation"
type="password"
name="password_confirmation"
autoComplete="new-password"
className="mt-1 block w-full"
placeholder="Confirm password"
id="password_confirmation"
type="password"
name="password_confirmation"
autoComplete="new-password"
className="mt-1 block w-full"
placeholder={t('auth.reset.confirm_password_placeholder')}
/>
<InputError message={errors.password_confirmation} className="mt-2" />
</div>
<Button type="submit" className="mt-4 w-full" disabled={processing}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
Reset password
{t('auth.reset.submit', 'Reset password')}
</Button>
</div>
)}