1// services/LeadService.js 2import { formService } from "./FormService.js"; 3import { i18n } from "./I18nService.js"; 4 5class LeadService { 6 getMessageFieldNames() { 7 return new Set(['name', 'email', 'phone', 'company', 'type', 'message', 'terms']); 8 } 9 10 getFieldElement(form, fieldName) { 11 return form.querySelector(`[name="${fieldName}"]`); 12 } 13 14 getFieldLabel(form, fieldName) { 15 const field = this.getFieldElement(form, fieldName); 16 const label = field?.closest('div')?.querySelector('label'); 17 18 if (!label) { 19 return fieldName; 20 } 21 22 return label.textContent 23 .replace('*', '') 24 .replace(/\s+/g, ' ') 25 .trim() 26 .replace(/:$/, ''); 27 } 28 29 getFieldDisplayValue(form, fieldName, rawValue) { 30 const field = this.getFieldElement(form, fieldName); 31 32 if (field instanceof HTMLSelectElement) { 33 return field.options[field.selectedIndex]?.text?.trim() || String(rawValue).trim(); 34 } 35 36 if (field instanceof HTMLInputElement && field.type === 'checkbox') { 37 return field.checked ? 'SÃ' : 'No'; 38 } 39 40 return String(rawValue).trim(); 41 } 42 43 buildLeadMessage(form, textValues) { 44 const cleanMessage = String(textValues.message || '').trim(); 45 const ignoredFields = this.getMessageFieldNames(); 46 47 const metadataLines = Object.entries(textValues) 48 .filter(([fieldName, fieldValue]) => { 49 if (ignoredFields.has(fieldName)) { 50 return false; 51 } 52 53 const normalizedValue = String(fieldValue || '').trim(); 54 const field = this.getFieldElement(form, fieldName); 55 56 if (!normalizedValue || !field) { 57 return false; 58 } 59 60 return !(field instanceof HTMLInputElement && field.type === 'hidden'); 61 }) 62 .map(([fieldName, fieldValue]) => { 63 const label = this.getFieldLabel(form, fieldName); 64 const displayValue = this.getFieldDisplayValue(form, fieldName, fieldValue); 65 66 return `${label}: ${displayValue}`; 67 }); 68 69 if (!metadataLines.length) { 70 return cleanMessage; 71 } 72 73 return `${metadataLines.join('\n')}\n\n${cleanMessage}`.trim(); 74 } 75 76 /** 77 * Configura un formulario para enviar leads al backend. 78 * Inyecta automáticamente el departamento para cumplir con el Enum de Strapi. 79 * @param {string} formId - ID del formulario HTML 80 * @param {string} departmentEnum - Valor del enum ('general', 'sales', 'exterior_shoots') 81 * @param {string} successId - ID del elemento de éxito a mostrar tras enviar 82 */ 83 initLeadForm(formId, departmentEnum, successId) { 84 const t = (key) => i18n.t(key); 85 const form = document.getElementById(formId); 86 const successEl = document.getElementById(successId); 87 88 if (!form) return; 89 90 // 1. Inyectamos el campo department (oculto) de forma programática 91 // Asà aseguramos que nadie desde el HTML manipule el valor y rompa el Enum 92 let deptInput = form.querySelector('input[name="type"]'); 93 if (!deptInput) { 94 deptInput = document.createElement('input'); 95 deptInput.type = 'hidden'; 96 deptInput.name = 'type'; 97 form.appendChild(deptInput); 98 } 99 deptInput.value = departmentEnum; 100 101 // Guardamos el formulario para repintarlo tras el éxito 102 const formInner = form.innerHTML; 103 104 // 2. Activamos las validaciones dinámicas 105 const submitBtn = form.querySelector('button[type="submit"]'); 106 if (submitBtn) { 107 formService.initValidatorEvent(form, submitBtn); 108 } 109 110 // 3. Capturamos el envÃo hacia el endpoint de Leads 111 form.addEventListener('submit', async (e) => { 112 e.preventDefault(); 113 submitBtn.disabled = true; 114 115 form.querySelectorAll('input, textarea').forEach(input => { 116 input.setAttribute('value', input.value); 117 118 if (input.tagName === 'TEXTAREA') { 119 input.textContent = input.value; 120 } 121 }); 122 let formValues = form.innerHTML; 123 124 lucide.createIcons(); 125 126 try { 127 const { textValues } = await formService.sanitizeForm(form); 128 const formattedMessage = this.buildLeadMessage(form, textValues); 129 130 const payload = { data: { 131 name: textValues.name, 132 email: textValues.email, 133 phone: textValues.phone || '', 134 company: textValues.company || '', 135 message: formattedMessage, 136 type: textValues.type, 137 } 138 }; 139 140 form.innerHTML = `<div class="col-span-full text-center py-12"> 141 <div class="flex justify-center items-center"> 142 <h3 class="col-span-full text-center text-slate-500 text-2xl font-black uppercase italic mr-4">${t('contact.sending_msg')}</h3> 143 <i class="animate-spin rounded-full h-10 w-10 text-slate-500" data-lucide="loader-circle"></i> 144 </div> 145 </div>` 146 147 const result = await formService.sendData('/api/leads', payload, 'POST'); 148 149 if (result) { 150 // Ãxito: Ocultamos el formulario y mostramos el check visual 151 form.classList.add('hidden'); 152 if (successEl) { 153 successEl.classList.remove('hidden'); 154 successEl.classList.add('animate-fade-in-up'); 155 if (window.lucide) window.lucide.createIcons(); 156 } 157 158 setTimeout(() => {successEl.classList.add('hidden')}, 1000); 159 setTimeout(() => {form.innerHTML = formInner}, 1000); 160 setTimeout(() => {form.classList.remove('hidden')}, 1000); 161 setTimeout(() => { 162 const submitBtn = form.querySelector('button[type="submit"]'); 163 if (submitBtn) { 164 formService.initValidatorEvent(form, submitBtn); 165 } 166 }, 1000) 167 } 168 } catch (error) { 169 form.innerHTML = formValues; 170 const submitBtn = form.querySelector('button[type="submit"]'); 171 if (submitBtn) { 172 formService.initValidatorEvent(form, submitBtn); 173 } 174 console.error("Error enviando lead:", error); 175 formService.errorEvent(error.status || 500); 176 } 177 }); 178 } 179 180 /**
181 * Función principal para inicializar todos los formularios de contacto de la página. 182 */ 183 initContactPageForms() { 184 // Mapeo exacto contra los ENUMS de api::lead.lead 185 this.initLeadForm('contact-form-general', 'general', 'success-general'); 186 this.initLeadForm('contact-form-sales', 'sales', 'success-sales'); 187 } 188} 189 190export const leadService = new LeadService();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.