nicer package layout, also in checkout step 1, fixed missing registration language strings, registration error handling, email verification redirect, email verification error handling and messaging,
This commit is contained in:
@@ -37,11 +37,37 @@ type RegisterFormFields = {
|
||||
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 resolveCsrfToken = (): string => {
|
||||
if (typeof document === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const metaToken = (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null)?.content;
|
||||
|
||||
if (metaToken && metaToken.length > 0) {
|
||||
return metaToken;
|
||||
}
|
||||
|
||||
return getCookieValue('XSRF-TOKEN') ?? '';
|
||||
};
|
||||
|
||||
export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale, prefill, onClearGoogleProfile }: 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<string | null>(null);
|
||||
const [serverErrorType, setServerErrorType] = useState<'generic' | 'session-expired'>('generic');
|
||||
const { t } = useTranslation(['auth', 'common']);
|
||||
const page = usePage<{ errors: Record<string, string>; locale?: string; auth?: { user?: any | null } }>();
|
||||
const resolvedLocale = locale ?? page.props.locale ?? 'de';
|
||||
@@ -68,6 +94,30 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
|
||||
|
||||
const registerEndpoint = '/checkout/register';
|
||||
|
||||
const requiredStringFields: Array<keyof RegisterFormFields> = useMemo(() => (
|
||||
['first_name', 'last_name', 'username', 'email', 'password', 'password_confirmation', 'address', 'phone']
|
||||
), []);
|
||||
|
||||
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(' ') : '';
|
||||
@@ -126,24 +176,28 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
|
||||
|
||||
const submit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
setServerError(null);
|
||||
setServerErrorType('generic');
|
||||
setHasTriedSubmit(true);
|
||||
setIsSubmitting(true);
|
||||
clearErrors();
|
||||
|
||||
const csrfToken = resolveCsrfToken();
|
||||
const body = {
|
||||
...data,
|
||||
locale: resolvedLocale,
|
||||
package_id: data.package_id ?? packageId ?? null,
|
||||
_token: csrfToken,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
const response = await fetch(registerEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-TOKEN': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null)?.content ?? '',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'X-XSRF-TOKEN': csrfToken,
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify(body),
|
||||
@@ -179,10 +233,32 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
|
||||
return;
|
||||
}
|
||||
|
||||
toast.error(t('register.unexpected_error', 'Registrierung nicht moeglich.'));
|
||||
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 (error) {
|
||||
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);
|
||||
toast.error(t('register.unexpected_error', 'Registrierung nicht moeglich.'));
|
||||
const fallbackMessage = t('register.server_error_message', 'Etwas ist schiefgelaufen. Bitte versuche es erneut.');
|
||||
setServerErrorType('generic');
|
||||
setServerError(fallbackMessage);
|
||||
toast.error(fallbackMessage);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -463,10 +539,21 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
|
||||
</div>
|
||||
)}
|
||||
|
||||
{serverError && (
|
||||
<div className="mb-6 rounded-md border border-red-200 bg-red-50 p-4 text-sm text-red-800">
|
||||
<p className="font-semibold">
|
||||
{serverErrorType === 'session-expired'
|
||||
? t('register.session_expired_title', 'Sicherheitsprüfung abgelaufen')
|
||||
: t('register.server_error_title', 'Registrierung konnte nicht abgeschlossen werden')}
|
||||
</p>
|
||||
<p className="mt-1">{serverError}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
disabled={isSubmitting}
|
||||
disabled={isSubmitting || !isFormValid}
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-[#FFB6C1] hover:bg-[#FF69B4] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#FFB6C1] transition duration-300 disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting && <LoaderCircle className="h-4 w-4 animate-spin mr-2" />}
|
||||
|
||||
Reference in New Issue
Block a user