export const RsvFormSender = { get_form_url(form_id) { return ReservairServiceAPI.restUrl + '/form/' + form_id; }, clear_feedback(form) { form.querySelectorAll('.rsv-field-error').forEach(el => el.remove()); form.querySelectorAll('.rsv-invalid').forEach(el => el.classList.remove('rsv-invalid')); form.querySelector('.rsv-error-summary')?.remove(); }, show_errors(form, errors) { this.clear_feedback(form); const ul = document.createElement('ul'); for (const err of errors) { const li = document.createElement('li'); li.textContent = err.message; ul.appendChild(li); if (err.element) { const field = form.querySelector(`[name="${err.element}"]`); if (field) { field.classList.add('rsv-invalid'); const msg = document.createElement('span'); msg.classList.add('rsv-field-error'); msg.textContent = err.message; field.insertAdjacentElement('afterend', msg); } } } const summary = document.createElement('div'); summary.classList.add('rsv-error-summary'); summary.appendChild(ul); form.prepend(summary); }, show_success(form, _data) { const s = ReservairStrings.form; const wrapper = form.parentElement; const existing = Array.from(wrapper.children); const svgNS = 'http://www.w3.org/2000/svg'; const path = document.createElementNS(svgNS, 'path'); path.setAttribute('d', 'M6 14l6 6L22 8'); path.setAttribute('stroke', '#16a34a'); path.setAttribute('stroke-width', '2.5'); path.setAttribute('stroke-linecap', 'round'); path.setAttribute('stroke-linejoin', 'round'); const svg = document.createElementNS(svgNS, 'svg'); svg.setAttribute('width', '28'); svg.setAttribute('height', '28'); svg.setAttribute('viewBox', '0 0 28 28'); svg.setAttribute('fill', 'none'); svg.appendChild(path); const icon = document.createElement('div'); icon.className = 'success-icon'; icon.appendChild(svg); const title = document.createElement('div'); title.className = 'success-title'; title.textContent = s.success_title; const subtitle = document.createElement('p'); subtitle.className = 'success-msg'; subtitle.textContent = s.success_subtitle; const reset_btn = document.createElement('button'); reset_btn.className = 'reset-btn'; reset_btn.textContent = s.new_reservation; const state = document.createElement('div'); state.className = 'success-state'; state.append(icon, title, subtitle, reset_btn); const msg = document.createElement('div'); msg.appendChild(state); existing.forEach(child => child.style.display = 'none'); wrapper.appendChild(msg); reset_btn.addEventListener('click', () => { msg.remove(); form.reset(); this.clear_feedback(form); existing.forEach(child => child.style.display = ''); }); }, set_loading(form, is_loading) { const btn = form.querySelector('button[type="submit"], button:not([type])'); if (!btn) return; btn.disabled = is_loading; btn.classList.toggle('rsv-loading', is_loading); }, encode_to_json(form) { const fields = form.querySelectorAll('.rsv-form-field'); const body = {}; fields.forEach(field => { const name = field.name ?? field.getAttribute('name'); try { body[name] = JSON.parse(field.value); } catch { body[name] = field.value; } }); return body; }, send_form(event) { event.preventDefault(); const form = event.target; this.clear_feedback(form); this.set_loading(form, true); const body = this.encode_to_json(form); fetch(this.get_form_url(form.id), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) .then(async response => { const data = await response.json().catch(() => null); if (!response.ok) throw { status: response.status, body: data }; return data; }) .then(data => { this.show_success(form, data); }) .catch(error => { const errors = error?.body?.errors ?? [{ element: '', message: ReservairStrings.form.error_generic }]; this.show_errors(form, errors); }) .finally(() => { this.set_loading(form, false); }); }, };