(function () { const EMAIL_STORAGE_KEY = 'ff_member_form_email_v1'; const DISCLAIMER_ACCEPTED_KEY = 'ff_member_form_disclaimer_accepted_v1'; const boot = window.APP_BOOT || { steps: [], csrf: '', contactEmail: '' }; const baseUrl = String(boot.baseUrl || '').replace(/\/+$/, ''); const state = { email: '', currentStep: 1, totalSteps: boot.steps.length, autosaveId: null, }; const disclaimerSection = document.getElementById('disclaimerSection'); const acceptDisclaimerBtn = document.getElementById('acceptDisclaimerBtn'); const startSection = document.getElementById('startSection'); const startForm = document.getElementById('startForm'); const startIntroText = document.getElementById('startIntroText'); const startEmailField = document.getElementById('startEmailField'); const startActions = document.getElementById('startActions'); const startEmailInput = document.getElementById('startEmail'); const resetDataBtn = document.getElementById('resetDataBtn'); const compactStatusBox = document.getElementById('compactStatusBox'); const statusEmailValue = document.getElementById('statusEmailValue'); const draftStatusValue = document.getElementById('draftStatusValue'); const feedbackMessage = document.getElementById('feedbackMessage'); const wizardSection = document.getElementById('wizardSection'); const applicationForm = document.getElementById('applicationForm'); const applicationEmail = document.getElementById('applicationEmail'); const progress = document.getElementById('progress'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); const submitBtn = document.getElementById('submitBtn'); const stepElements = Array.from(document.querySelectorAll('.step')); function appUrl(path) { const normalizedPath = String(path || '').replace(/^\/+/, ''); return (baseUrl ? baseUrl + '/' : '/') + normalizedPath; } function setFeedback(text, isError) { feedbackMessage.textContent = text || ''; feedbackMessage.classList.toggle('error-text', Boolean(isError)); } function setDraftStatus(text, isError) { draftStatusValue.textContent = text || ''; draftStatusValue.classList.toggle('error-text', Boolean(isError)); } function formatTimestamp(isoDate) { if (!isoDate) { return ''; } const date = new Date(isoDate); if (Number.isNaN(date.getTime())) { return String(isoDate); } return date.toLocaleString('de-DE'); } function normalizeEmail(email) { return (email || '').trim().toLowerCase(); } function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } function rememberEmail(email) { try { localStorage.setItem(EMAIL_STORAGE_KEY, email); } catch (_err) { // ignore localStorage errors } } function getRememberedEmail() { try { return localStorage.getItem(EMAIL_STORAGE_KEY) || ''; } catch (_err) { return ''; } } function forgetRememberedEmail() { try { localStorage.removeItem(EMAIL_STORAGE_KEY); } catch (_err) { // ignore localStorage errors } } function hasAcceptedDisclaimer() { try { return localStorage.getItem(DISCLAIMER_ACCEPTED_KEY) === '1'; } catch (_err) { return false; } } function setDisclaimerAccepted() { try { localStorage.setItem(DISCLAIMER_ACCEPTED_KEY, '1'); } catch (_err) { // ignore localStorage errors } } function enterCompactStatus(email) { statusEmailValue.textContent = email; startSection.classList.add('compact-mode'); compactStatusBox.classList.remove('hidden'); startIntroText.classList.add('hidden'); startEmailField.classList.add('hidden'); startActions.classList.add('hidden'); } function leaveCompactStatus() { startSection.classList.remove('compact-mode'); compactStatusBox.classList.add('hidden'); startIntroText.classList.remove('hidden'); startEmailField.classList.remove('hidden'); startActions.classList.remove('hidden'); statusEmailValue.textContent = '-'; setDraftStatus('Noch nicht gespeichert', false); } function lockEmail(email) { state.email = email; applicationEmail.value = email; startEmailInput.value = email; startEmailInput.readOnly = true; startEmailInput.setAttribute('aria-readonly', 'true'); rememberEmail(email); enterCompactStatus(email); } function unlockEmail(clearInput) { state.email = ''; applicationEmail.value = ''; startEmailInput.readOnly = false; startEmailInput.removeAttribute('aria-readonly'); if (clearInput) { startEmailInput.value = ''; } forgetRememberedEmail(); leaveCompactStatus(); } function stopAutosave() { if (state.autosaveId) { clearInterval(state.autosaveId); state.autosaveId = null; } } function startAutosave() { stopAutosave(); state.autosaveId = setInterval(async () => { if (!state.email || wizardSection.classList.contains('hidden')) { return; } try { await saveDraft(false); } catch (_err) { // visible on next manual action } }, 15000); } function clearErrors() { document.querySelectorAll('[data-error-for]').forEach((el) => { el.textContent = ''; }); } function showErrors(errors) { clearErrors(); Object.keys(errors || {}).forEach((key) => { const el = document.querySelector('[data-error-for="' + key + '"]'); if (el) { el.textContent = errors[key]; } }); } function clearWizardData() { applicationForm.reset(); clearErrors(); renderUploadInfo({}); state.currentStep = 1; submitBtn.disabled = false; nextBtn.disabled = false; prevBtn.disabled = false; updateProgress(); Array.from(document.querySelectorAll('[data-upload-key]')).forEach((control) => { const fieldKey = control.getAttribute('data-upload-key'); if (fieldKey) { updateUploadSelectionText(fieldKey); } }); } function updateProgress() { progress.textContent = 'Schritt ' + state.currentStep + ' von ' + state.totalSteps; stepElements.forEach((el) => { const step = Number(el.getAttribute('data-step')); el.classList.toggle('hidden', step !== state.currentStep); }); prevBtn.disabled = state.currentStep === 1; nextBtn.classList.toggle('hidden', state.currentStep === state.totalSteps); submitBtn.classList.toggle('hidden', state.currentStep !== state.totalSteps); } function fillFormData(data) { Object.keys(data || {}).forEach((key) => { const field = applicationForm.querySelector('[name="form_data[' + key + ']"]'); if (!field) return; if (field.type === 'checkbox') { field.checked = ['1', 'on', 'true', true].includes(data[key]); } else { field.value = data[key] || ''; } }); } function renderUploadInfo(uploads) { document.querySelectorAll('[data-upload-list]').forEach((el) => { el.innerHTML = ''; }); Object.keys(uploads || {}).forEach((field) => { const target = document.querySelector('[data-upload-list="' + field + '"]'); if (!target || !Array.isArray(uploads[field])) return; uploads[field].forEach((item) => { const div = document.createElement('div'); div.className = 'upload-item'; div.textContent = item.original_filename + ' (' + item.uploaded_at + ')'; target.appendChild(div); }); }); } function updateUploadSelectionText(fieldKey) { const target = document.querySelector('[data-upload-selected="' + fieldKey + '"]'); if (!target) { return; } const fileInput = applicationForm.querySelector('[name="' + fieldKey + '"]'); const cameraInput = applicationForm.querySelector('[name="' + fieldKey + '__camera"]'); let label = 'Keine Datei gewählt'; if (fileInput && fileInput.files && fileInput.files[0]) { label = 'Ausgewählt: ' + fileInput.files[0].name; } if (cameraInput && cameraInput.files && cameraInput.files[0]) { label = 'Ausgewählt: ' + cameraInput.files[0].name + ' (Foto)'; } target.textContent = label; } async function triggerInstantUpload() { if (!state.email || wizardSection.classList.contains('hidden')) { return; } setDraftStatus('Datei wird hochgeladen ...', false); try { await saveDraft(true); setFeedback('', false); } catch (err) { const msg = (err.payload && err.payload.message) || err.message || 'Upload fehlgeschlagen.'; setDraftStatus('Upload fehlgeschlagen', true); setFeedback(msg, true); } } function initUploadControls() { const controls = Array.from(document.querySelectorAll('[data-upload-key]')); controls.forEach((control) => { const fieldKey = control.getAttribute('data-upload-key') || ''; if (!fieldKey) { return; } const fileInput = applicationForm.querySelector('[name="' + fieldKey + '"]'); const cameraInput = applicationForm.querySelector('[name="' + fieldKey + '__camera"]'); if (fileInput) { fileInput.addEventListener('change', async () => { if (fileInput.files && fileInput.files[0] && cameraInput) { cameraInput.value = ''; } updateUploadSelectionText(fieldKey); if (fileInput.files && fileInput.files[0]) { await triggerInstantUpload(); } }); } if (cameraInput) { cameraInput.addEventListener('change', async () => { if (cameraInput.files && cameraInput.files[0] && fileInput) { fileInput.value = ''; } updateUploadSelectionText(fieldKey); if (cameraInput.files && cameraInput.files[0]) { await triggerInstantUpload(); } }); } updateUploadSelectionText(fieldKey); }); } async function postForm(url, formData) { const response = await fetch(url, { method: 'POST', body: formData, credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' }, }); const payload = await response.json(); if (!response.ok || payload.ok === false) { const err = new Error(payload.message || 'Anfrage fehlgeschlagen'); err.payload = payload; throw err; } return payload; } function collectPayload(includeFiles) { const fd = new FormData(); fd.append('csrf', boot.csrf); fd.append('email', state.email); fd.append('step', String(state.currentStep)); fd.append('website', ''); Array.from(applicationForm.elements).forEach((el) => { if (!el.name) return; if (!el.name.startsWith('form_data[')) return; if (el.type === 'checkbox') { fd.append(el.name, el.checked ? '1' : '0'); } else { fd.append(el.name, el.value || ''); } }); if (includeFiles) { Array.from(applicationForm.querySelectorAll('input[type="file"]')).forEach((input) => { if (input.files && input.files[0]) { fd.append(input.name, input.files[0]); } }); } return fd; } async function loadDraft(email) { const fd = new FormData(); fd.append('csrf', boot.csrf); fd.append('email', email); fd.append('website', ''); return postForm(appUrl('api/load-draft.php'), fd); } async function resetSavedData(email) { const fd = new FormData(); fd.append('csrf', boot.csrf); fd.append('email', email); fd.append('website', ''); return postForm(appUrl('api/reset.php'), fd); } async function saveDraft(includeFiles) { const payload = collectPayload(includeFiles); const response = await postForm(appUrl('api/save-draft.php'), payload); if (response.upload_errors && Object.keys(response.upload_errors).length > 0) { showErrors(response.upload_errors); setDraftStatus('Uploadfehler', true); setFeedback('Einige Dateien konnten nicht gespeichert werden.', true); } else { const ts = formatTimestamp(response.updated_at); setDraftStatus('Gespeichert: ' + (ts || 'gerade eben'), false); } if (response.uploads) { renderUploadInfo(response.uploads); } if (includeFiles) { Array.from(applicationForm.querySelectorAll('input[type="file"]')).forEach((input) => { input.value = ''; }); Array.from(document.querySelectorAll('[data-upload-key]')).forEach((control) => { const fieldKey = control.getAttribute('data-upload-key'); if (fieldKey) { updateUploadSelectionText(fieldKey); } }); } return response; } async function submitApplication() { const payload = collectPayload(true); const response = await postForm(appUrl('api/submit.php'), payload); clearErrors(); setDraftStatus('Abgeschlossen', false); setFeedback('Antrag erfolgreich abgeschlossen. Vielen Dank.', false); submitBtn.disabled = true; nextBtn.disabled = true; prevBtn.disabled = true; return response; } async function startProcess(rawEmail) { const email = normalizeEmail(rawEmail); if (!isValidEmail(email)) { setFeedback('Bitte eine gültige E-Mail-Adresse eingeben.', true); startEmailInput.focus(); return; } try { const result = await loadDraft(email); lockEmail(email); if (result.already_submitted) { wizardSection.classList.add('hidden'); setDraftStatus('Antrag bereits abgeschlossen', false); setFeedback(boot.contactEmail ? 'Kontakt: ' + boot.contactEmail : '', false); stopAutosave(); return; } wizardSection.classList.remove('hidden'); fillFormData(result.data || {}); renderUploadInfo(result.uploads || {}); state.currentStep = Math.min(Math.max(Number(result.step || 1), 1), state.totalSteps); updateProgress(); startAutosave(); const loadedAt = formatTimestamp(result.updated_at); if (loadedAt) { setDraftStatus('Entwurf geladen: ' + loadedAt, false); } else { setDraftStatus('Neuer Entwurf gestartet', false); } setFeedback('', false); } catch (err) { const msg = (err.payload && err.payload.message) || err.message || 'Laden fehlgeschlagen.'; setFeedback(msg, true); } } startForm.addEventListener('submit', async (event) => { event.preventDefault(); await startProcess(startEmailInput.value || ''); }); resetDataBtn.addEventListener('click', async () => { if (!state.email) { setFeedback('Keine aktive E-Mail vorhanden.', true); return; } const confirmed = window.confirm('Alle gespeicherten Daten zu dieser E-Mail endgültig löschen und neu starten?'); if (!confirmed) { return; } try { await resetSavedData(state.email); stopAutosave(); wizardSection.classList.add('hidden'); clearWizardData(); unlockEmail(true); setFeedback('Alle gespeicherten Daten wurden gelöscht. Sie können neu starten.', false); startEmailInput.focus(); } catch (err) { const msg = (err.payload && err.payload.message) || err.message || 'Löschen fehlgeschlagen.'; setFeedback(msg, true); } }); prevBtn.addEventListener('click', async () => { if (state.currentStep <= 1) return; try { await saveDraft(false); state.currentStep -= 1; updateProgress(); } catch (err) { const msg = (err.payload && err.payload.message) || err.message; setFeedback(msg, true); } }); nextBtn.addEventListener('click', async () => { if (state.currentStep >= state.totalSteps) return; try { await saveDraft(false); state.currentStep += 1; updateProgress(); } catch (err) { const msg = (err.payload && err.payload.message) || err.message; setFeedback(msg, true); } }); submitBtn.addEventListener('click', async () => { try { await submitApplication(); } catch (err) { const payload = err.payload || {}; if (payload.errors) { showErrors(payload.errors); } const msg = payload.message || err.message || 'Absenden fehlgeschlagen.'; setFeedback(msg, true); if (payload.already_submitted) { wizardSection.classList.add('hidden'); setDraftStatus('Antrag bereits abgeschlossen', false); } } }); function initializeAfterDisclaimer() { disclaimerSection.classList.add('hidden'); startSection.classList.remove('hidden'); const rememberedEmail = normalizeEmail(getRememberedEmail()); if (rememberedEmail !== '') { if (isValidEmail(rememberedEmail)) { startEmailInput.value = rememberedEmail; setFeedback('', false); startProcess(rememberedEmail); } else { forgetRememberedEmail(); } } } if (acceptDisclaimerBtn) { acceptDisclaimerBtn.addEventListener('click', () => { setDisclaimerAccepted(); initializeAfterDisclaimer(); }); } if (hasAcceptedDisclaimer()) { initializeAfterDisclaimer(); } else { disclaimerSection.classList.remove('hidden'); startSection.classList.add('hidden'); } initUploadControls(); updateProgress(); })();