import React, { useEffect, useMemo, useState } from 'react'; import { useForm, usePage } from '@inertiajs/react'; import { useTranslation } from 'react-i18next'; import toast from 'react-hot-toast'; import { LoaderCircle, User, Mail, Lock } from 'lucide-react'; import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import type { OAuthProfilePrefill } from '../marketing/checkout/types'; export interface RegisterSuccessPayload { user: unknown | null; redirect?: string | null; pending_purchase?: boolean; } interface RegisterFormProps { packageId?: number; onSuccess?: (payload: RegisterSuccessPayload) => void; privacyHtml: string; locale?: string; prefill?: OAuthProfilePrefill; onClearPrefill?: () => void; } type RegisterFormFields = { email: string; password: string; password_confirmation: string; first_name: string; last_name: string; privacy_consent: boolean; terms: boolean; package_id: number | null; }; const getCookieValue = (name: string): string | null => { if (typeof document === 'undefined') { return null; } const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)); return match ? decodeURIComponent(match[1]) : null; }; const resolveMetaCsrfToken = (): string => { if (typeof document === 'undefined') { return ''; } return (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null)?.content ?? ''; }; export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale, prefill, onClearPrefill }: RegisterFormProps) { const [privacyOpen, setPrivacyOpen] = useState(false); const [hasTriedSubmit, setHasTriedSubmit] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [prefillApplied, setPrefillApplied] = useState(false); const [serverError, setServerError] = useState(null); const [serverErrorType, setServerErrorType] = useState<'generic' | 'session-expired'>('generic'); const { t } = useTranslation(['auth', 'common']); const page = usePage<{ errors: Record; locale?: string; auth?: { user?: unknown | null } }>(); const resolvedLocale = locale ?? page.props.locale ?? 'de'; const { data, setData, errors, clearErrors, reset, setError } = useForm({ email: '', password: '', password_confirmation: '', first_name: '', last_name: '', privacy_consent: false, terms: false, package_id: packageId || null, }); useEffect(() => { if (hasTriedSubmit && Object.keys(errors).length > 0) { toast.error(Object.values(errors).join(' ')); } }, [errors, hasTriedSubmit]); const registerEndpoint = '/checkout/register'; const requiredStringFields: Array = useMemo(() => ( ['first_name', 'last_name', 'email', 'password', 'password_confirmation'] ), []); const isFormValid = useMemo(() => { const stringsValid = requiredStringFields.every((field) => { const value = data[field]; if (typeof value !== 'string') { return false; } return value.trim().length > 0; }); if (!stringsValid) { return false; } const emailValid = /.+@.+\..+/.test(data.email.trim()); const passwordsMatch = data.password === data.password_confirmation; return emailValid && passwordsMatch && data.privacy_consent && data.terms; }, [data, requiredStringFields]); const namePrefill = useMemo(() => { const rawFirst = prefill?.given_name ?? prefill?.name?.split(' ')[0] ?? ''; const remaining = prefill?.name ? prefill.name.split(' ').slice(1).join(' ') : ''; const rawLast = prefill?.family_name ?? remaining; return { first: rawFirst ?? '', last: rawLast ?? '', }; }, [prefill]); useEffect(() => { if (!prefill || prefillApplied) { return; } if (namePrefill.first && !data.first_name) { setData('first_name', namePrefill.first); } if (namePrefill.last && !data.last_name) { setData('last_name', namePrefill.last); } if (prefill.email && !data.email) { setData('email', prefill.email); } setPrefillApplied(true); }, [prefill, namePrefill.first, namePrefill.last, data.first_name, data.last_name, data.email, prefillApplied, setData]); const submit = async (event: React.FormEvent) => { event.preventDefault(); setServerError(null); setServerErrorType('generic'); setHasTriedSubmit(true); setIsSubmitting(true); clearErrors(); const metaToken = resolveMetaCsrfToken(); const cookieToken = getCookieValue('XSRF-TOKEN'); const body = { ...data, locale: resolvedLocale, package_id: data.package_id ?? packageId ?? null, _token: metaToken || undefined, }; try { const response = await fetch(registerEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(cookieToken ? { 'X-XSRF-TOKEN': cookieToken } : metaToken ? { 'X-CSRF-TOKEN': metaToken } : {}), }, credentials: 'same-origin', body: JSON.stringify(body), }); if (response.ok) { const json = await response.json(); toast.success(t('register.success_toast', 'Registrierung erfolgreich')); onSuccess?.({ user: json?.user ?? null, redirect: json?.redirect ?? null, pending_purchase: json?.pending_purchase ?? json?.user?.pending_purchase ?? false, }); onClearPrefill?.(); reset(); setHasTriedSubmit(false); return; } if (response.status === 422) { const json = await response.json(); const fieldErrors = json?.errors ?? {}; Object.entries(fieldErrors).forEach(([key, message]) => { if (key in data) { const fieldKey = key as keyof RegisterFormFields; const firstMessage = Array.isArray(message) ? (message[0] as string | undefined) : typeof message === 'string' ? message : undefined; if (firstMessage) { setError(fieldKey, firstMessage); } } }); toast.error(t('register.validation_failed', 'Bitte pruefen Sie Ihre Eingaben.')); return; } if (response.status === 419) { const expiredMessage = t('register.session_expired_message', 'Deine Sitzung ist abgelaufen. Bitte lade die Seite neu und versuche es erneut.'); setServerErrorType('session-expired'); setServerError(expiredMessage); toast.error(expiredMessage); return; } let message: string | null = null; try { const json = await response.clone().json(); message = json?.message ?? null; } catch { message = null; } const fallbackMessage = message ?? t('register.server_error_message', 'Etwas ist schiefgelaufen. Bitte versuche es erneut.'); setServerErrorType('generic'); setServerError(fallbackMessage); toast.error(fallbackMessage); } catch (error) { console.error('Register request failed', error); const fallbackMessage = t('register.server_error_message', 'Etwas ist schiefgelaufen. Bitte versuche es erneut.'); setServerErrorType('generic'); setServerError(fallbackMessage); toast.error(fallbackMessage); } finally { setIsSubmitting(false); } }; useEffect(() => { if (!hasTriedSubmit) { return; } const errorKeys = Object.keys(errors); if (errorKeys.length === 0) { return; } const firstError = errorKeys[0]; const field = document.querySelector(`[name="${firstError}"]`); if (field) { field.scrollIntoView({ behavior: 'smooth', block: 'center' }); field.focus(); } }, [errors, hasTriedSubmit]); return (
{ setData('first_name', e.target.value); if (e.target.value.trim() && errors.first_name) { clearErrors('first_name'); } }} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.first_name ? 'border-red-500' : 'border-gray-300'}`} placeholder={t('register.first_name_placeholder')} />
{errors.first_name &&

{errors.first_name}

}
{ setData('last_name', e.target.value); if (e.target.value.trim() && errors.last_name) { clearErrors('last_name'); } }} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.last_name ? 'border-red-500' : 'border-gray-300'}`} placeholder={t('register.last_name_placeholder')} />
{errors.last_name &&

{errors.last_name}

}
{ setData('email', e.target.value); if (e.target.value.trim() && errors.email) { clearErrors('email'); } }} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.email ? 'border-red-500' : 'border-gray-300'}`} placeholder={t('register.email_placeholder')} />
{errors.email &&

{errors.email}

}
{ setData('password', e.target.value); if (e.target.value.trim() && errors.password) { clearErrors('password'); } if (data.password_confirmation && e.target.value === data.password_confirmation) { clearErrors('password_confirmation'); } }} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.password ? 'border-red-500' : 'border-gray-300'}`} placeholder={t('register.password_placeholder')} />
{errors.password &&

{errors.password}

}
{ setData('password_confirmation', e.target.value); if (e.target.value.trim() && errors.password_confirmation) { clearErrors('password_confirmation'); } if (data.password && e.target.value === data.password) { clearErrors('password_confirmation'); } }} className={`block w-full pl-10 pr-3 py-3 border rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#FFB6C1] focus:border-[#FFB6C1] sm:text-sm ${errors.password_confirmation ? 'border-red-500' : 'border-gray-300'}`} placeholder={t('register.password_confirmation_placeholder')} />
{errors.password_confirmation &&

{errors.password_confirmation}

}
{ setData('privacy_consent', e.target.checked); setData('terms', e.target.checked); if (e.target.checked && errors.privacy_consent) { clearErrors('privacy_consent'); } if (e.target.checked && errors.terms) { clearErrors('terms'); } }} className="h-4 w-4 text-[#FFB6C1] focus:ring-[#FFB6C1] border-gray-300 rounded" /> {errors.privacy_consent &&

{errors.privacy_consent}

} {errors.terms &&

{errors.terms}

}
{Object.keys(errors).length > 0 && (

{t('register.errors_title')}

    {Object.entries(errors).map(([key, value]) => (
  • {t(`register.errors.${key}`)}: {value}
  • ))}
)} {serverError && (

{serverErrorType === 'session-expired' ? t('register.session_expired_title', 'Sicherheitsprüfung abgelaufen') : t('register.server_error_title', 'Registrierung konnte nicht abgeschlossen werden')}

{serverError}

)} Datenschutzerklärung Lesen Sie unsere Datenschutzerklärung.
); }