codex has reworked checkout, but frontend doesnt work

This commit is contained in:
Codex Agent
2025-10-05 20:39:30 +02:00
parent fdaa2bec62
commit d70faf7a9d
35 changed files with 2105 additions and 430 deletions

View File

@@ -1,98 +1,181 @@
import React, { useEffect, useState } from 'react';
import { useForm, usePage } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import toast from 'react-hot-toast';
import { LoaderCircle, Mail, Lock } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import InputError from '@/components/input-error';
import TextLink from '@/components/text-link';
import React, { useEffect, useMemo, useState } from "react";
import { usePage } from "@inertiajs/react";
import { useTranslation } from "react-i18next";
import toast from "react-hot-toast";
import { LoaderCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import InputError from "@/components/input-error";
import TextLink from "@/components/text-link";
interface LoginFormProps {
onSuccess?: (userData: any) => void;
canResetPassword?: boolean;
declare const route: (name: string, params?: Record<string, unknown>) => string;
export interface AuthUserPayload {
id?: number;
email?: string;
name?: string | null;
pending_purchase?: boolean;
}
export default function LoginForm({ onSuccess, canResetPassword = true }: LoginFormProps) {
const [hasTriedSubmit, setHasTriedSubmit] = useState(false);
const { t } = useTranslation('auth');
const { props } = usePage<{ errors: Record<string, string> }>();
interface LoginFormProps {
onSuccess?: (userData: AuthUserPayload | null) => void;
canResetPassword?: boolean;
locale?: string;
}
const { data, setData, post, processing, errors, clearErrors, reset } = useForm({
email: '',
password: '',
type SharedPageProps = {
locale?: string;
};
type FieldErrors = Record<string, string>;
const fallbackRoute = (locale: string) => `/${locale}/login`;
const csrfToken = () => (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null)?.content ?? "";
export default function LoginForm({ onSuccess, canResetPassword = true, locale }: LoginFormProps) {
const page = usePage<SharedPageProps>();
const { t } = useTranslation("auth");
const resolvedLocale = locale ?? page.props.locale ?? "de";
const loginEndpoint = useMemo(() => {
if (typeof route === "function") {
try {
return route("checkout.login");
} catch (error) {
// Ziggy might not be booted yet; fall back to locale-aware path.
}
}
return fallbackRoute(resolvedLocale);
}, [resolvedLocale]);
const [values, setValues] = useState({
email: "",
password: "",
remember: false,
});
useEffect(() => {
if (hasTriedSubmit && Object.keys(errors).length > 0) {
toast.error(Object.values(errors).join(' '));
}
}, [errors, hasTriedSubmit]);
const submit = (e: React.FormEvent) => {
e.preventDefault();
setHasTriedSubmit(true);
post('/login', {
preserveScroll: true,
onSuccess: () => {
if (onSuccess) {
onSuccess({ user: { email: data.email } }); // Pass basic user info; full user from props in parent
}
reset();
},
});
};
const [errors, setErrors] = useState<FieldErrors>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [hasTriedSubmit, setHasTriedSubmit] = useState(false);
useEffect(() => {
if (!hasTriedSubmit) {
return;
}
const errorKeys = Object.keys(errors);
if (errorKeys.length === 0) {
return;
}
const field = document.querySelector<HTMLInputElement>(`[name="${errorKeys[0]}"]`);
if (field) {
field.scrollIntoView({ behavior: 'smooth', block: 'center' });
field.focus();
const collected = Object.values(errors).filter(Boolean);
if (collected.length > 0) {
toast.error(collected.join(" \u2022 "));
}
}, [errors, hasTriedSubmit]);
useEffect(() => {
if (!hasTriedSubmit) {
return;
}
const firstKey = Object.keys(errors).find((key) => Boolean(errors[key]));
if (!firstKey) {
return;
}
const field = document.querySelector<HTMLInputElement>(`[name="${firstKey}"]`);
field?.scrollIntoView({ behavior: "smooth", block: "center" });
field?.focus();
}, [errors, hasTriedSubmit]);
const updateValue = (key: keyof typeof values, value: string | boolean) => {
setValues((current) => ({ ...current, [key]: value }));
setErrors((current) => ({ ...current, [key as string]: "" }));
};
const submit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setHasTriedSubmit(true);
setErrors({});
setIsSubmitting(true);
try {
const response = await fetch(loginEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-CSRF-TOKEN": csrfToken(),
},
credentials: "same-origin",
body: JSON.stringify({
email: values.email,
password: values.password,
remember: values.remember,
locale: resolvedLocale,
}),
});
if (response.ok) {
const payload = await response.json();
const nextUser: AuthUserPayload | null = payload?.user ?? payload?.data ?? null;
toast.success(t("login.success_toast", "Login erfolgreich"));
onSuccess?.(nextUser);
setValues((current) => ({ ...current, password: "" }));
setHasTriedSubmit(false);
return;
}
if (response.status === 422) {
const payload = await response.json();
const fieldErrors: FieldErrors = {};
const source = payload?.errors ?? {};
Object.entries(source).forEach(([key, message]) => {
if (Array.isArray(message) && message.length > 0) {
fieldErrors[key] = String(message[0]);
} else if (typeof message === "string") {
fieldErrors[key] = message;
}
});
setErrors(fieldErrors);
toast.error(t("login.failed_generic", "Ungueltige Anmeldedaten"));
return;
}
toast.error(t("login.unexpected_error", "Beim Login ist ein Fehler aufgetreten."));
} catch (error) {
console.error("Login request failed", error);
toast.error(t("login.unexpected_error", "Beim Login ist ein Fehler aufgetreten."));
} finally {
setIsSubmitting(false);
}
};
return (
<div className="flex flex-col gap-6">
<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="email">{t("login.email")}</Label>
<Input
id="email"
type="email"
name="email"
required
autoFocus
placeholder={t('login.email_placeholder')}
value={data.email}
onChange={(e) => {
setData('email', e.target.value);
if (errors.email) {
clearErrors('email');
}
}}
placeholder={t("login.email_placeholder")}
value={values.email}
onChange={(event) => updateValue("email", event.target.value)}
/>
<InputError message={errors.email} />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">{t('login.password')}</Label>
<Label htmlFor="password">{t("login.password")}</Label>
{canResetPassword && (
<TextLink href="/forgot-password" className="ml-auto text-sm">
{t('login.forgot')}
<TextLink href={typeof route === "function" ? route("password.request") : "/forgot-password"} className="ml-auto text-sm">
{t("login.forgot")}
</TextLink>
)}
</div>
@@ -101,14 +184,9 @@ export default function LoginForm({ onSuccess, canResetPassword = true }: LoginF
type="password"
name="password"
required
placeholder={t('login.password_placeholder')}
value={data.password}
onChange={(e) => {
setData('password', e.target.value);
if (errors.password) {
clearErrors('password');
}
}}
placeholder={t("login.password_placeholder")}
value={values.password}
onChange={(event) => updateValue("password", event.target.value)}
/>
<InputError message={errors.password} />
</div>
@@ -117,25 +195,24 @@ export default function LoginForm({ onSuccess, canResetPassword = true }: LoginF
<Checkbox
id="remember"
name="remember"
checked={data.remember}
onCheckedChange={(checked) => setData('remember', Boolean(checked))}
checked={values.remember}
onCheckedChange={(checked) => updateValue("remember", Boolean(checked))}
/>
<Label htmlFor="remember">{t('login.remember')}</Label>
<Label htmlFor="remember">{t("login.remember")}</Label>
</div>
<Button type="button" onClick={submit} className="w-full" disabled={processing}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin mr-2" />}
{t('login.submit')}
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting && <LoaderCircle className="h-4 w-4 animate-spin mr-2" />}
{t("login.submit")}
</Button>
</div>
{Object.keys(errors).length > 0 && (
{Object.values(errors).some(Boolean) && (
<div className="p-4 bg-red-50 border border-red-200 rounded-md">
<p className="text-sm text-red-800">
{Object.values(errors).join(' ')}
</p>
<p className="text-sm text-red-800">{Object.values(errors).filter(Boolean).join(" \u2022 ")}</p>
</div>
)}
</div>
</form>
);
}
}