/** * Newsletter Subscription Component * Reusable modal for newsletter subscriptions across the website */ class TractausNewsletter { constructor() { this.isOpen = false; this.isMobile = window.matchMedia('(max-width: 768px)').matches; this.csrfToken = null; this.init(); } async init() { this.renderModal(); this.attachEventListeners(); await this.fetchCsrfToken(); } async fetchCsrfToken() { try { const response = await fetch('/api/csrf-token'); const data = await response.json(); this.csrfToken = data.csrfToken; } catch (error) { console.error('[Newsletter] Failed to fetch CSRF token:', error); } } renderModal() { const modalHTML = `
`; document.body.insertAdjacentHTML('beforeend', modalHTML); } attachEventListeners() { const modal = document.getElementById('newsletter-modal'); const backdrop = document.getElementById('newsletter-backdrop'); const closeBtn = document.getElementById('newsletter-close'); const form = document.getElementById('newsletter-form'); const doneBtn = document.getElementById('newsletter-done-btn'); const retryBtn = document.getElementById('newsletter-retry-btn'); // Close handlers backdrop.addEventListener('click', () => this.close()); closeBtn.addEventListener('click', () => this.close()); doneBtn.addEventListener('click', () => this.close()); retryBtn.addEventListener('click', () => this.showStep('form')); // Form submission form.addEventListener('submit', (e) => { e.preventDefault(); this.handleSubmit(e); }); // Escape key to close document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen) { this.close(); } }); // Global trigger - look for elements with data-newsletter-trigger document.addEventListener('click', (e) => { if (e.target.closest('[data-newsletter-trigger]')) { e.preventDefault(); this.open(); } }); } open() { const modal = document.getElementById('newsletter-modal'); modal.classList.remove('hidden'); this.isOpen = true; document.body.classList.add('overflow-hidden'); this.showStep('form'); } close() { const modal = document.getElementById('newsletter-modal'); modal.classList.add('hidden'); this.isOpen = false; document.body.classList.remove('overflow-hidden'); // Reset form const form = document.getElementById('newsletter-form'); form.reset(); // Re-check the research interest by default const researchCheckbox = form.querySelector('input[value="research"]'); if (researchCheckbox) { researchCheckbox.checked = true; } } showStep(step) { const steps = { form: 'newsletter-form-step', success: 'newsletter-success-step', error: 'newsletter-error-step' }; Object.values(steps).forEach(stepId => { const el = document.getElementById(stepId); if (el) el.classList.add('hidden'); }); const targetStep = document.getElementById(steps[step]); if (targetStep) { targetStep.classList.remove('hidden'); } } async handleSubmit(e) { const form = e.target; const submitBtn = form.querySelector('button[type="submit"]'); const originalBtnText = submitBtn.textContent; try { // Disable submit button submitBtn.disabled = true; submitBtn.textContent = 'Subscribing...'; // Collect form data const formData = new FormData(form); const email = formData.get('email'); const name = formData.get('name'); const interestCheckboxes = form.querySelectorAll('input[name="interest"]:checked'); const interests = Array.from(interestCheckboxes).map(cb => cb.value); // Prepare request data const data = { email, name: name || undefined, source: window.location.pathname, interests }; // Submit to API const response = await fetch('/api/newsletter/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': this.csrfToken }, credentials: 'include', body: JSON.stringify(data) }); const result = await response.json(); if (response.ok && result.success) { this.showStep('success'); } else { throw new Error(result.error || 'Subscription failed'); } } catch (error) { console.error('[Newsletter] Subscription error:', error); const errorMsg = document.getElementById('newsletter-error-message'); errorMsg.textContent = error.message || 'We couldn\'t process your subscription. Please try again.'; this.showStep('error'); } finally { submitBtn.disabled = false; submitBtn.textContent = originalBtnText; } } } // Auto-initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { window.tractausNewsletter = new TractausNewsletter(); }); } else { window.tractausNewsletter = new TractausNewsletter(); }