Corregge destinatario contatti e cursore sul pulsante Invia.

Ignora override INFO malformati, preferisce il mittente newsletter e usa cursor-pointer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Javaxman
2026-07-29 17:19:47 +02:00
parent 866d07b8b2
commit 90058f3e3e
2 changed files with 46 additions and 15 deletions

View File

@ -230,7 +230,7 @@ const fieldClass =
<div class="pt-1"> <div class="pt-1">
<button <button
type="submit" type="submit"
class="inline-flex h-10 w-full items-center justify-center rounded-lg bg-nx-fg px-5 text-sm font-semibold text-nx-bg hover:bg-white sm:w-auto sm:min-w-[10rem]" class="inline-flex h-10 w-full cursor-pointer items-center justify-center rounded-lg bg-nx-fg px-5 text-sm font-semibold text-nx-bg hover:bg-white sm:w-auto sm:min-w-[10rem]"
> >
{cf.submitLabel} {cf.submitLabel}
</button> </button>

View File

@ -13,10 +13,16 @@ function getRuntimeEnv(): RuntimeEnv {
function readEnv(name: string, runtimeEnv: RuntimeEnv): string { function readEnv(name: string, runtimeEnv: RuntimeEnv): string {
const runtimeValue = runtimeEnv[name]; const runtimeValue = runtimeEnv[name];
if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) { if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) {
return runtimeValue.trim(); return runtimeValue.trim().replace(/^['"]+|['"]+$/g, '').trim();
} }
const staticValue = import.meta.env[name]; const staticValue = import.meta.env[name];
return typeof staticValue === 'string' ? staticValue.trim() : ''; return typeof staticValue === 'string'
? staticValue.trim().replace(/^['"]+|['"]+$/g, '').trim()
: '';
}
function looksLikeEmail(value: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
} }
function sanitizeRedirect(candidate: string, requestUrl: URL): string { function sanitizeRedirect(candidate: string, requestUrl: URL): string {
@ -87,23 +93,36 @@ async function verifyTurnstile(token: string, secret: string, ip?: string | null
return payload.success === true; return payload.success === true;
} }
function resolveDepartmentRecipient(runtimeEnv: RuntimeEnv, defaultRecipient: string, department: string): string { /**
* Destinatario per area. Override solo se l'indirizzo è valido;
* altrimenti usa sempre CONTACT_FORM_TO_EMAIL (evita secret INFO/PRIVACY malformati).
*/
function resolveDepartmentRecipient(
runtimeEnv: RuntimeEnv,
defaultRecipient: string,
department: string,
): string {
const normalized = department.trim().toLowerCase(); const normalized = department.trim().toLowerCase();
const map: Record<string, string> = { if (!normalized || normalized === 'info') {
info: readEnv('CONTACT_FORM_TO_EMAIL_INFO', runtimeEnv) || defaultRecipient, return defaultRecipient;
privacy: readEnv('CONTACT_FORM_TO_EMAIL_PRIVACY', runtimeEnv), }
careers: readEnv('CONTACT_FORM_TO_EMAIL_CAREERS', runtimeEnv), const override =
}; normalized === 'privacy'
return map[normalized] || defaultRecipient; ? readEnv('CONTACT_FORM_TO_EMAIL_PRIVACY', runtimeEnv)
: normalized === 'careers'
? readEnv('CONTACT_FORM_TO_EMAIL_CAREERS', runtimeEnv)
: '';
return looksLikeEmail(override) ? override : defaultRecipient;
} }
export const POST: APIRoute = async ({ request, url }) => { export const POST: APIRoute = async ({ request, url }) => {
try { try {
const runtimeEnv = getRuntimeEnv(); const runtimeEnv = getRuntimeEnv();
const mode = readEnv('CONTACT_FORM_MODE', runtimeEnv).toLowerCase() || 'dev'; const mode = readEnv('CONTACT_FORM_MODE', runtimeEnv).toLowerCase() || 'dev';
// Preferisci il mittente newsletter (secret già usato con successo) sul Variable del form.
const fromEmail = const fromEmail =
readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv) || readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv) ||
readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv); readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv);
const toEmail = const toEmail =
readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv) || readEnv('CONTACT_FORM_TO_EMAIL', runtimeEnv) ||
readEnv('NEWSLETTER_NOTIFY_TO_EMAIL', runtimeEnv); readEnv('NEWSLETTER_NOTIFY_TO_EMAIL', runtimeEnv);
@ -176,8 +195,12 @@ export const POST: APIRoute = async ({ request, url }) => {
return fail(request, USER_SEND_ERROR, 500); return fail(request, USER_SEND_ERROR, 500);
} }
const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department); const destination = resolveDepartmentRecipient(runtimeEnv, toEmail, department);
if (!destination) { if (!looksLikeEmail(fromEmail) || !looksLikeEmail(destination)) {
console.error('[contact] empty department recipient', { department }); console.error('[contact] invalid from/to email shape', {
fromOk: looksLikeEmail(fromEmail),
toOk: looksLikeEmail(destination),
department,
});
return fail(request, USER_SEND_ERROR, 500); return fail(request, USER_SEND_ERROR, 500);
} }
@ -190,7 +213,13 @@ export const POST: APIRoute = async ({ request, url }) => {
replyTo: email, replyTo: email,
}); });
} catch (error) { } catch (error) {
console.error('[contact] email failed', error); console.error('[contact] email failed', {
code: emailErrorCode(error),
fromDomain: fromEmail.split('@')[1] || '',
toDomain: destination.split('@')[1] || '',
department,
error,
});
// Retry without replyTo: some payloads reject Reply-To even when From/To are valid. // Retry without replyTo: some payloads reject Reply-To even when From/To are valid.
try { try {
await sendCloudflareEmail(emailBinding, { await sendCloudflareEmail(emailBinding, {
@ -202,6 +231,8 @@ export const POST: APIRoute = async ({ request, url }) => {
} catch (retryError) { } catch (retryError) {
console.error('[contact] email retry failed', { console.error('[contact] email retry failed', {
code: emailErrorCode(retryError), code: emailErrorCode(retryError),
fromDomain: fromEmail.split('@')[1] || '',
toDomain: destination.split('@')[1] || '',
error: retryError, error: retryError,
}); });
return fail(request, userEmailErrorMessage(retryError), 502); return fail(request, userEmailErrorMessage(retryError), 502);