PageSourceSearch

https://www.ciudaddelaluz.com/services/LeadService.js

js ciudaddelaluz.com collected 2026-10-02 12:57:25 UTC 7,331 bytes, 190 lines download raw bytes

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.