Messaggi errore contatti user-friendly #22

Merged
javaxman merged 1 commits from fix/contact-user-friendly-errors into main 2026-07-29 17:01:57 +02:00
Showing only changes of commit 9e06181f37 - Show all commits

View File

@ -37,6 +37,9 @@ function wantsJson(request: Request): boolean {
return (request.headers.get('Accept') || '').includes('application/json'); return (request.headers.get('Accept') || '').includes('application/json');
} }
const USER_SEND_ERROR =
'Impossibile inviare il messaggio in questo momento. Riprova più tardi.';
function fail(request: Request, message: string, status: number): Response { function fail(request: Request, message: string, status: number): Response {
if (wantsJson(request)) { if (wantsJson(request)) {
return Response.json({ ok: false, error: message }, { status }); return Response.json({ ok: false, error: message }, { status });
@ -51,38 +54,21 @@ function succeed(request: Request, redirectTarget: string): Response {
return Response.redirect(redirectTarget, 303); return Response.redirect(redirectTarget, 303);
} }
function emailErrorMessage(error: unknown): string { function emailErrorCode(error: unknown): string {
const code = if (error && typeof error === 'object' && 'code' in error) {
error && typeof error === 'object' && 'code' in error return String((error as { code?: unknown }).code || '');
? String((error as { code?: unknown }).code || '') }
: ''; return '';
const message = }
error instanceof Error
? error.message
: error && typeof error === 'object' && 'message' in error
? String((error as { message?: unknown }).message || '')
: '';
function userEmailErrorMessage(error: unknown): string {
const code = emailErrorCode(error);
switch (code) { switch (code) {
case 'E_SENDER_NOT_VERIFIED':
case 'E_SENDER_DOMAIN_NOT_AVAILABLE':
return 'Dominio mittente non verificato su Cloudflare Email Service.';
case 'E_RECIPIENT_NOT_ALLOWED':
return 'Destinatario non autorizzato dal binding email.';
case 'E_RECIPIENT_SUPPRESSED':
return 'Destinatario in lista di soppressione (bounce/spam).';
case 'E_RATE_LIMIT_EXCEEDED': case 'E_RATE_LIMIT_EXCEEDED':
case 'E_DAILY_LIMIT_EXCEEDED': case 'E_DAILY_LIMIT_EXCEEDED':
return 'Limite di invio email raggiunto. Riprova più tardi.'; return 'Troppe richieste al momento. Riprova tra poco.';
case 'E_VALIDATION_ERROR':
case 'E_FIELD_MISSING':
return message
? `Dati email non validi: ${message}`
: 'Dati email non validi.';
default: default:
return code return USER_SEND_ERROR;
? `Invio email non riuscito (${code}). Riprova più tardi.`
: 'Invio email non riuscito. Riprova più tardi.';
} }
} }
@ -146,7 +132,7 @@ export const POST: APIRoute = async ({ request, url }) => {
const message = required(form.get('message')); const message = required(form.get('message'));
if (!name || !email || !country || !message) { if (!name || !email || !country || !message) {
return fail(request, 'Missing required contact fields.', 400); return fail(request, 'Compila tutti i campi obbligatori.', 400);
} }
const turnstileToken = required(form.get('cf-turnstile-response')); const turnstileToken = required(form.get('cf-turnstile-response'));
@ -157,7 +143,11 @@ export const POST: APIRoute = async ({ request, url }) => {
request.headers.get('CF-Connecting-IP'), request.headers.get('CF-Connecting-IP'),
); );
if (!isHuman) { if (!isHuman) {
return fail(request, 'Turnstile verification failed.', 400); return fail(
request,
'Completa il controllo anti-spam e riprova.',
400,
);
} }
} }
@ -179,18 +169,16 @@ export const POST: APIRoute = async ({ request, url }) => {
if (!emailBinding || !fromEmail || !toEmail) { if (!emailBinding || !fromEmail || !toEmail) {
const missing = [ const missing = [
!emailBinding ? 'EMAIL binding' : '', !emailBinding ? 'EMAIL binding' : '',
!fromEmail ? 'CONTACT_FORM_FROM_EMAIL (o NEWSLETTER_FROM_EMAIL)' : '', !fromEmail ? 'CONTACT_FORM_FROM_EMAIL|NEWSLETTER_FROM_EMAIL' : '',
!toEmail ? 'CONTACT_FORM_TO_EMAIL (o NEWSLETTER_NOTIFY_TO_EMAIL)' : '', !toEmail ? 'CONTACT_FORM_TO_EMAIL|NEWSLETTER_NOTIFY_TO_EMAIL' : '',
].filter(Boolean); ].filter(Boolean);
return fail( console.error('[contact] incomplete email config', { missing });
request, return fail(request, USER_SEND_ERROR, 500);
`Config email incompleta: manca ${missing.join(', ')}. Imposta le variabili runtime del Worker (Settings → Variables and Secrets), non Build.`,
500,
);
} }
const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department); const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department);
if (!destination) { if (!destination) {
return fail(request, 'Nessun destinatario configurato per il reparto selezionato.', 500); console.error('[contact] empty department recipient', { department });
return fail(request, USER_SEND_ERROR, 500);
} }
try { try {
@ -212,8 +200,11 @@ export const POST: APIRoute = async ({ request, url }) => {
text, text,
}); });
} catch (retryError) { } catch (retryError) {
console.error('[contact] email retry failed', retryError); console.error('[contact] email retry failed', {
return fail(request, emailErrorMessage(retryError), 502); code: emailErrorCode(retryError),
error: retryError,
});
return fail(request, userEmailErrorMessage(retryError), 502);
} }
} }
} else { } else {
@ -230,6 +221,6 @@ export const POST: APIRoute = async ({ request, url }) => {
return succeed(request, redirectTarget); return succeed(request, redirectTarget);
} catch (error) { } catch (error) {
console.error('[contact] unexpected error', error); console.error('[contact] unexpected error', error);
return fail(request, 'Errore interno. Riprova più tardi.', 500); return fail(request, USER_SEND_ERROR, 500);
} }
}; };