Fix falso successo form contatti + conferma UI #25

Merged
javaxman merged 1 commits from fix/contact-silent-success-honeypot into main 2026-07-29 17:34:26 +02:00
6 changed files with 45 additions and 29 deletions

View File

@ -6,8 +6,8 @@ PUBLIC_SITE_URL=http://localhost:4321
CONTACT_FORM_MODE=dev
CONTACT_FORM_FROM_EMAIL=no-reply@nexstudio.ai
CONTACT_FORM_TO_EMAIL=javaxman69@gmail.com
CONTACT_FORM_TO_EMAIL_INFO=javaxman69@gmail.com
CONTACT_FORM_TO_EMAIL=pro@nexstudio.ai
CONTACT_FORM_TO_EMAIL_INFO=
CONTACT_FORM_TO_EMAIL_PRIVACY=
CONTACT_FORM_TO_EMAIL_CAREERS=
CONTACT_TURNSTILE_SECRET_KEY=
@ -16,6 +16,6 @@ CONTACT_TURNSTILE_SECRET_KEY=
# In produzione: NEWSLETTER_MODE=live (oppure CONTACT_FORM_MODE=live come fallback)
NEWSLETTER_MODE=dev
NEWSLETTER_FROM_EMAIL=no-reply@nexstudio.ai
NEWSLETTER_NOTIFY_TO_EMAIL=javaxman69@gmail.com
NEWSLETTER_NOTIFY_TO_EMAIL=pro@nexstudio.ai
NEWSLETTER_ADMIN_TOKEN=
NEWSLETTER_TURNSTILE_SECRET_KEY=

View File

@ -19,6 +19,7 @@ const model = {
fieldDepartment: cf.fieldDepartment,
fieldEmail: cf.fieldEmail,
fieldMessage: cf.fieldMessage,
successMessage: cf.successMessage,
turnstileError: cf.errors.turnstile,
configError: cf.errors.config,
mailtoFallbackSubject: cf.mailtoFallbackSubject,
@ -112,18 +113,6 @@ const fieldClass =
<input type="hidden" name="_subject" value={notifySubject} />
) : null
}
{
hasFormspree ? (
<input
type="text"
name="_gotcha"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
class="pointer-events-none absolute -left-[9999px] h-0 w-0 opacity-0"
/>
) : null
}
<div>
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-name">
@ -203,6 +192,13 @@ const fieldClass =
></textarea>
</div>
<p
id="nx-contact-form-success"
class="hidden text-sm leading-relaxed text-emerald-400/90"
role="status"
>
</p>
<p
id="nx-contact-form-error"
class="hidden text-sm leading-relaxed text-red-400/90"

View File

@ -27,6 +27,7 @@ const ctaByLocale = {
emailLabel: 'Email',
messageLabel: 'Scrivi qui il tuo messaggio',
submitLabel: 'Invia messaggio',
successMessage: 'Messaggio inviato. Ti risponderemo al più presto.',
actionUrl: contactActionUrl,
successRedirect: contactSuccessRedirect,
turnstileSiteKey: contactTurnstileSiteKey,
@ -78,6 +79,7 @@ const ctaByLocale = {
emailLabel: 'Email',
messageLabel: 'Write your message',
submitLabel: 'Send message',
successMessage: 'Message sent. We will get back to you soon.',
actionUrl: contactActionUrl,
successRedirect: contactSuccessRedirect,
turnstileSiteKey: contactTurnstileSiteKey,
@ -129,6 +131,7 @@ const ctaByLocale = {
emailLabel: 'อีเมล',
messageLabel: 'พิมพ์ข้อความของคุณ',
submitLabel: 'ส่งข้อความ',
successMessage: 'ส่งข้อความแล้ว เราจะติดต่อกลับโดยเร็ว',
actionUrl: contactActionUrl,
successRedirect: contactSuccessRedirect,
turnstileSiteKey: contactTurnstileSiteKey,

View File

@ -129,14 +129,7 @@ export const POST: APIRoute = async ({ request, url }) => {
const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv);
const form = await request.formData();
if (required(form.get('_gotcha'))) {
const redirectTarget =
sanitizeRedirect(required(form.get('_next')), url) ||
sanitizeRedirect(request.headers.get('referer') ?? '/', url) ||
new URL('/#contatti', url).toString();
return succeed(request, redirectTarget);
}
// Honeypot rimosso dal form (Turnstile basta). Se un client legacy lo manda, ignoralo.
const redirectTarget =
sanitizeRedirect(required(form.get('_next')), url) ||
sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) ||
@ -239,6 +232,14 @@ export const POST: APIRoute = async ({ request, url }) => {
}
}
} else {
const isProdHost =
url.hostname === 'nexstudio.ai' ||
url.hostname.endsWith('.nexstudio.ai') ||
url.hostname.endsWith('.workers.dev');
console.warn('[contact] CONTACT_FORM_MODE is not live', { mode, host: url.hostname });
if (isProdHost) {
return fail(request, USER_SEND_ERROR, 503);
}
console.info('[contact:dev] Contact payload received', {
name,
company,

View File

@ -5,6 +5,7 @@ interface ContactFormCfg {
turnstileSiteKey: string;
notifySubject: string;
successRedirect: string;
successMessage: string;
fieldName: string;
fieldCompany: string;
fieldCountry: string;
@ -32,6 +33,7 @@ function readCfg(): ContactFormCfg {
turnstileSiteKey: '',
notifySubject: '',
successRedirect: '',
successMessage: 'Message sent.',
fieldName: 'name',
fieldCompany: 'company',
fieldCountry: 'country',
@ -53,12 +55,23 @@ function readCfg(): ContactFormCfg {
}
function showError(message: string): void {
const ok = document.getElementById('nx-contact-form-success');
if (ok) ok.classList.add('hidden');
const err = document.getElementById('nx-contact-form-error');
if (!err) return;
err.textContent = message;
err.classList.remove('hidden');
}
function showSuccess(message: string): void {
const err = document.getElementById('nx-contact-form-error');
if (err) err.classList.add('hidden');
const ok = document.getElementById('nx-contact-form-success');
if (!ok) return;
ok.textContent = message;
ok.classList.remove('hidden');
}
/** Dopo un POST fallito il token Turnstile è già consumato: ne serve uno nuovo. */
function resetTurnstile(form: HTMLFormElement): void {
const widget = form.querySelector<HTMLElement>('.cf-turnstile');
@ -89,7 +102,9 @@ function bind(): void {
form.addEventListener('submit', async (e) => {
const cfg = readCfg();
const err = document.getElementById('nx-contact-form-error');
const ok = document.getElementById('nx-contact-form-success');
if (err) err.classList.add('hidden');
if (ok) ok.classList.add('hidden');
const hasTurnstile =
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
@ -125,11 +140,12 @@ function bind(): void {
if (hasTurnstile) resetTurnstile(form);
return;
}
const target =
(data && typeof data.redirect === 'string' && data.redirect) ||
cfg.successRedirect ||
`${window.location.pathname}${window.location.search}#contatti`;
window.location.assign(target);
form.reset();
if (hasTurnstile) resetTurnstile(form);
showSuccess(
cfg.successMessage || 'Messaggio inviato. Ti risponderemo al più presto.',
);
form.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} catch {
showError('Errore di rete. Controlla la connessione e riprova.');
if (hasTurnstile) resetTurnstile(form);

View File

@ -12,7 +12,7 @@
},
"vars": {
"CONTACT_FORM_FROM_EMAIL": "no-reply@nexstudio.ai",
"CONTACT_FORM_TO_EMAIL": "javaxman69@gmail.com"
"CONTACT_FORM_TO_EMAIL": "pro@nexstudio.ai"
},
"d1_databases": [
{