import {createContactGuard,contactStorage} from './contact-antibot.mjs'; import {contactRecipient,contactEndpoint} from './contact-config.mjs'; const validEmail=value=>typeof value==='string'&&value.length<=254&&!/[\r\n]/.test(value)&&/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(value); export function prepareContactEmail({recipient,name,email,organisation='',message,language='fr'}){ if(!validEmail(recipient)||!validEmail(email))throw new Error('Invalid email'); if(typeof name!=='string'||!name.trim()||name.length>120||typeof organisation!=='string'||organisation.length>160||typeof message!=='string'||!message.trim()||message.length>5000)throw new Error('Invalid enquiry'); const en=language==='en'; const subject=en?'Inknowframe — transformation enquiry':'Inknowframe — demande d’échange'; const body=[(en?'Name: ':'Nom : ')+name.trim(),(en?'Email: ':'E-mail : ')+email.trim(),(en?'Organisation: ':'Organisation : ')+organisation.trim(),'',(en?'My project / transformation:':'Mon projet / ma transformation :'),message.trim()].join('\n'); return 'mailto:'+encodeURIComponent(recipient)+'?subject='+encodeURIComponent(subject)+'&body='+encodeURIComponent(body); } export async function sendContactEnquiry(endpoint,enquiry,{fetchImpl=globalThis.fetch,timeoutMs=15000}={}){ if(typeof endpoint!=='string'||!(/^(https:\/\/|\/(?!\/))/.test(endpoint)))throw Error('Invalid endpoint'); const controller=new AbortController();const timer=setTimeout(()=>controller.abort(),timeoutMs); try{ const response=await fetchImpl(endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(enquiry),credentials:'omit',signal:controller.signal}); const result=await response.json(); if(!response.ok||result?.accepted!==true||typeof result.reference!=='string'||!result.reference.trim()||result.reference.length>120)throw Error('Delivery not accepted'); return result.reference; }finally{clearTimeout(timer);} } export function bindContactForm(doc,recipient,navigate,options={}){ const form=doc.getElementById('contact-form');if(!form)return; const submit=doc.getElementById('contact-submit'),note=doc.getElementById('contact-delivery-note'),status=doc.getElementById('contact-status'); const guard=createContactGuard({now:options.now??Date.now,storage:options.storage??contactStorage()}); const en=doc.documentElement.lang==='en';const endpoint=options.endpoint??'';let pending=false;submit.disabled=!validEmail(recipient); if(endpoint)note.textContent=en?'Your enquiry is sent through our delivery service.':'Votre demande est transmise par notre service d’envoi.'; if(!submit.disabled&&!endpoint)note.textContent=en?'This form prepares an email to '+recipient+' in your email app. You send it from there.':'Ce formulaire prépare un e-mail à '+recipient+' dans votre messagerie. Vous l’envoyez ensuite depuis celle-ci.'; form.addEventListener('submit',async event=>{ event.preventDefault();if(pending||submit.disabled||!form.reportValidity())return; const blocked=guard.check(form.elements.namedItem('website')?.value??''); if(blocked){ status.hidden=false; const messages=en?{ fast:'Please wait a few seconds before preparing your email.', cooldown:'Please wait 30 seconds between attempts.', limit:'Too many attempts. Please try again in 15 minutes.', trap:'The form could not be processed. Please reload the page and try again.' }:{ fast:'Veuillez patienter quelques secondes avant de préparer votre e-mail.', cooldown:'Veuillez patienter 30 secondes entre deux tentatives.', limit:'Trop de tentatives. Veuillez réessayer dans 15 minutes.', trap:'Le formulaire ne peut pas être traité. Rechargez la page et réessayez.' }; status.textContent=messages[blocked];return; } try{ const field=name=>form.elements.namedItem(name).value; if(endpoint){ pending=true;submit.disabled=true;status.hidden=false;status.textContent=en?'Sending your enquiry…':'Transmission de votre demande…'; try{ const reference=await sendContactEnquiry(endpoint,{name:field('name'),email:field('email').trim(),organisation:field('organisation'),message:field('message'),website:field('website'),language:en?'en':'fr'},{fetchImpl:options.fetchImpl??globalThis.fetch}); guard.record();status.textContent=(en?'Accepted for sending. Reference: ':'Demande acceptée pour envoi. Référence : ')+reference; }catch{status.textContent=en?'Sending could not be confirmed. Your text is preserved. Please retry or use the email or booking link below.':'L’envoi n’a pas pu être confirmé. Votre saisie est conservée. Réessayez ou utilisez le lien e-mail ou de réservation ci-dessous.';} finally{pending=false;submit.disabled=false;} return; } const href=prepareContactEmail({recipient,name:field('name'),email:field('email'),organisation:field('organisation'),message:field('message'),language:en?'en':'fr'}); guard.record();navigate(href);status.hidden=false;status.textContent=en?'Your email is prepared. Send it from your email app.':'Votre e-mail est préparé. Envoyez-le depuis votre messagerie.'; }catch{status.hidden=false;status.textContent=en?'Please check the required fields.':'Veuillez vérifier les champs obligatoires.';} }); } if(typeof document!=='undefined')bindContactForm(document,contactRecipient,href=>{window.location.href=href;},{endpoint:contactEndpoint});