Elimina il falso successo del form contatti e mostra conferma invio.
Rimuove l honeypot autofillato, rifiuta mode non-live in produzione e TO su pro@nexstudio.ai. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -6,8 +6,8 @@ PUBLIC_SITE_URL=http://localhost:4321
|
|||||||
|
|
||||||
CONTACT_FORM_MODE=dev
|
CONTACT_FORM_MODE=dev
|
||||||
CONTACT_FORM_FROM_EMAIL=no-reply@nexstudio.ai
|
CONTACT_FORM_FROM_EMAIL=no-reply@nexstudio.ai
|
||||||
CONTACT_FORM_TO_EMAIL=javaxman69@gmail.com
|
CONTACT_FORM_TO_EMAIL=pro@nexstudio.ai
|
||||||
CONTACT_FORM_TO_EMAIL_INFO=javaxman69@gmail.com
|
CONTACT_FORM_TO_EMAIL_INFO=
|
||||||
CONTACT_FORM_TO_EMAIL_PRIVACY=
|
CONTACT_FORM_TO_EMAIL_PRIVACY=
|
||||||
CONTACT_FORM_TO_EMAIL_CAREERS=
|
CONTACT_FORM_TO_EMAIL_CAREERS=
|
||||||
CONTACT_TURNSTILE_SECRET_KEY=
|
CONTACT_TURNSTILE_SECRET_KEY=
|
||||||
@ -16,6 +16,6 @@ CONTACT_TURNSTILE_SECRET_KEY=
|
|||||||
# In produzione: NEWSLETTER_MODE=live (oppure CONTACT_FORM_MODE=live come fallback)
|
# In produzione: NEWSLETTER_MODE=live (oppure CONTACT_FORM_MODE=live come fallback)
|
||||||
NEWSLETTER_MODE=dev
|
NEWSLETTER_MODE=dev
|
||||||
NEWSLETTER_FROM_EMAIL=no-reply@nexstudio.ai
|
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_ADMIN_TOKEN=
|
||||||
NEWSLETTER_TURNSTILE_SECRET_KEY=
|
NEWSLETTER_TURNSTILE_SECRET_KEY=
|
||||||
|
|||||||
@ -19,6 +19,7 @@ const model = {
|
|||||||
fieldDepartment: cf.fieldDepartment,
|
fieldDepartment: cf.fieldDepartment,
|
||||||
fieldEmail: cf.fieldEmail,
|
fieldEmail: cf.fieldEmail,
|
||||||
fieldMessage: cf.fieldMessage,
|
fieldMessage: cf.fieldMessage,
|
||||||
|
successMessage: cf.successMessage,
|
||||||
turnstileError: cf.errors.turnstile,
|
turnstileError: cf.errors.turnstile,
|
||||||
configError: cf.errors.config,
|
configError: cf.errors.config,
|
||||||
mailtoFallbackSubject: cf.mailtoFallbackSubject,
|
mailtoFallbackSubject: cf.mailtoFallbackSubject,
|
||||||
@ -112,18 +113,6 @@ const fieldClass =
|
|||||||
<input type="hidden" name="_subject" value={notifySubject} />
|
<input type="hidden" name="_subject" value={notifySubject} />
|
||||||
) : null
|
) : 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>
|
<div>
|
||||||
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-name">
|
<label class="block text-xs font-medium text-nx-muted" for="nx-cf-name">
|
||||||
@ -203,6 +192,13 @@ const fieldClass =
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
id="nx-contact-form-success"
|
||||||
|
class="hidden text-sm leading-relaxed text-emerald-400/90"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
id="nx-contact-form-error"
|
id="nx-contact-form-error"
|
||||||
class="hidden text-sm leading-relaxed text-red-400/90"
|
class="hidden text-sm leading-relaxed text-red-400/90"
|
||||||
|
|||||||
@ -27,6 +27,7 @@ const ctaByLocale = {
|
|||||||
emailLabel: 'Email',
|
emailLabel: 'Email',
|
||||||
messageLabel: 'Scrivi qui il tuo messaggio',
|
messageLabel: 'Scrivi qui il tuo messaggio',
|
||||||
submitLabel: 'Invia messaggio',
|
submitLabel: 'Invia messaggio',
|
||||||
|
successMessage: 'Messaggio inviato. Ti risponderemo al più presto.',
|
||||||
actionUrl: contactActionUrl,
|
actionUrl: contactActionUrl,
|
||||||
successRedirect: contactSuccessRedirect,
|
successRedirect: contactSuccessRedirect,
|
||||||
turnstileSiteKey: contactTurnstileSiteKey,
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
@ -78,6 +79,7 @@ const ctaByLocale = {
|
|||||||
emailLabel: 'Email',
|
emailLabel: 'Email',
|
||||||
messageLabel: 'Write your message',
|
messageLabel: 'Write your message',
|
||||||
submitLabel: 'Send message',
|
submitLabel: 'Send message',
|
||||||
|
successMessage: 'Message sent. We will get back to you soon.',
|
||||||
actionUrl: contactActionUrl,
|
actionUrl: contactActionUrl,
|
||||||
successRedirect: contactSuccessRedirect,
|
successRedirect: contactSuccessRedirect,
|
||||||
turnstileSiteKey: contactTurnstileSiteKey,
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
@ -129,6 +131,7 @@ const ctaByLocale = {
|
|||||||
emailLabel: 'อีเมล',
|
emailLabel: 'อีเมล',
|
||||||
messageLabel: 'พิมพ์ข้อความของคุณ',
|
messageLabel: 'พิมพ์ข้อความของคุณ',
|
||||||
submitLabel: 'ส่งข้อความ',
|
submitLabel: 'ส่งข้อความ',
|
||||||
|
successMessage: 'ส่งข้อความแล้ว เราจะติดต่อกลับโดยเร็ว',
|
||||||
actionUrl: contactActionUrl,
|
actionUrl: contactActionUrl,
|
||||||
successRedirect: contactSuccessRedirect,
|
successRedirect: contactSuccessRedirect,
|
||||||
turnstileSiteKey: contactTurnstileSiteKey,
|
turnstileSiteKey: contactTurnstileSiteKey,
|
||||||
|
|||||||
@ -129,14 +129,7 @@ export const POST: APIRoute = async ({ request, url }) => {
|
|||||||
const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv);
|
const turnstileSecret = readEnv('CONTACT_TURNSTILE_SECRET_KEY', runtimeEnv);
|
||||||
|
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
if (required(form.get('_gotcha'))) {
|
// Honeypot rimosso dal form (Turnstile basta). Se un client legacy lo manda, ignoralo.
|
||||||
const redirectTarget =
|
|
||||||
sanitizeRedirect(required(form.get('_next')), url) ||
|
|
||||||
sanitizeRedirect(request.headers.get('referer') ?? '/', url) ||
|
|
||||||
new URL('/#contatti', url).toString();
|
|
||||||
return succeed(request, redirectTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectTarget =
|
const redirectTarget =
|
||||||
sanitizeRedirect(required(form.get('_next')), url) ||
|
sanitizeRedirect(required(form.get('_next')), url) ||
|
||||||
sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) ||
|
sanitizeRedirect(readEnv('PUBLIC_CONTACT_FORM_SUCCESS_REDIRECT', runtimeEnv), url) ||
|
||||||
@ -239,6 +232,14 @@ export const POST: APIRoute = async ({ request, url }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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', {
|
console.info('[contact:dev] Contact payload received', {
|
||||||
name,
|
name,
|
||||||
company,
|
company,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ interface ContactFormCfg {
|
|||||||
turnstileSiteKey: string;
|
turnstileSiteKey: string;
|
||||||
notifySubject: string;
|
notifySubject: string;
|
||||||
successRedirect: string;
|
successRedirect: string;
|
||||||
|
successMessage: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
fieldCompany: string;
|
fieldCompany: string;
|
||||||
fieldCountry: string;
|
fieldCountry: string;
|
||||||
@ -32,6 +33,7 @@ function readCfg(): ContactFormCfg {
|
|||||||
turnstileSiteKey: '',
|
turnstileSiteKey: '',
|
||||||
notifySubject: '',
|
notifySubject: '',
|
||||||
successRedirect: '',
|
successRedirect: '',
|
||||||
|
successMessage: 'Message sent.',
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
fieldCompany: 'company',
|
fieldCompany: 'company',
|
||||||
fieldCountry: 'country',
|
fieldCountry: 'country',
|
||||||
@ -53,12 +55,23 @@ function readCfg(): ContactFormCfg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showError(message: string): void {
|
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');
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
if (!err) return;
|
if (!err) return;
|
||||||
err.textContent = message;
|
err.textContent = message;
|
||||||
err.classList.remove('hidden');
|
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. */
|
/** Dopo un POST fallito il token Turnstile è già consumato: ne serve uno nuovo. */
|
||||||
function resetTurnstile(form: HTMLFormElement): void {
|
function resetTurnstile(form: HTMLFormElement): void {
|
||||||
const widget = form.querySelector<HTMLElement>('.cf-turnstile');
|
const widget = form.querySelector<HTMLElement>('.cf-turnstile');
|
||||||
@ -89,7 +102,9 @@ function bind(): void {
|
|||||||
form.addEventListener('submit', async (e) => {
|
form.addEventListener('submit', async (e) => {
|
||||||
const cfg = readCfg();
|
const cfg = readCfg();
|
||||||
const err = document.getElementById('nx-contact-form-error');
|
const err = document.getElementById('nx-contact-form-error');
|
||||||
|
const ok = document.getElementById('nx-contact-form-success');
|
||||||
if (err) err.classList.add('hidden');
|
if (err) err.classList.add('hidden');
|
||||||
|
if (ok) ok.classList.add('hidden');
|
||||||
|
|
||||||
const hasTurnstile =
|
const hasTurnstile =
|
||||||
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
|
typeof cfg.turnstileSiteKey === 'string' && cfg.turnstileSiteKey.length > 0;
|
||||||
@ -125,11 +140,12 @@ function bind(): void {
|
|||||||
if (hasTurnstile) resetTurnstile(form);
|
if (hasTurnstile) resetTurnstile(form);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target =
|
form.reset();
|
||||||
(data && typeof data.redirect === 'string' && data.redirect) ||
|
if (hasTurnstile) resetTurnstile(form);
|
||||||
cfg.successRedirect ||
|
showSuccess(
|
||||||
`${window.location.pathname}${window.location.search}#contatti`;
|
cfg.successMessage || 'Messaggio inviato. Ti risponderemo al più presto.',
|
||||||
window.location.assign(target);
|
);
|
||||||
|
form.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
} catch {
|
} catch {
|
||||||
showError('Errore di rete. Controlla la connessione e riprova.');
|
showError('Errore di rete. Controlla la connessione e riprova.');
|
||||||
if (hasTurnstile) resetTurnstile(form);
|
if (hasTurnstile) resetTurnstile(form);
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"vars": {
|
"vars": {
|
||||||
"CONTACT_FORM_FROM_EMAIL": "no-reply@nexstudio.ai",
|
"CONTACT_FORM_FROM_EMAIL": "no-reply@nexstudio.ai",
|
||||||
"CONTACT_FORM_TO_EMAIL": "javaxman69@gmail.com"
|
"CONTACT_FORM_TO_EMAIL": "pro@nexstudio.ai"
|
||||||
},
|
},
|
||||||
"d1_databases": [
|
"d1_databases": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user