|
|
@@ -0,0 +1,287 @@
|
|
|
+(function () {
|
|
|
+ const boot = window.APP_BOOT || { steps: [], csrf: '' };
|
|
|
+ const state = {
|
|
|
+ email: '',
|
|
|
+ currentStep: 1,
|
|
|
+ totalSteps: boot.steps.length,
|
|
|
+ autosaveId: null,
|
|
|
+ };
|
|
|
+
|
|
|
+ const startForm = document.getElementById('startForm');
|
|
|
+ const wizardSection = document.getElementById('wizardSection');
|
|
|
+ const blockedSection = document.getElementById('blockedSection');
|
|
|
+ const statusSection = document.getElementById('statusSection');
|
|
|
+ const statusMessage = document.getElementById('statusMessage');
|
|
|
+ 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 uploadNowBtn = document.getElementById('uploadNowBtn');
|
|
|
+
|
|
|
+ const stepElements = Array.from(document.querySelectorAll('.step'));
|
|
|
+
|
|
|
+ function showMessage(text) {
|
|
|
+ statusSection.classList.remove('hidden');
|
|
|
+ statusMessage.textContent = text;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 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);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ 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('/api/load-draft.php', fd);
|
|
|
+ }
|
|
|
+
|
|
|
+ async function saveDraft(includeFiles, showSavedText) {
|
|
|
+ const payload = collectPayload(includeFiles);
|
|
|
+ const response = await postForm('/api/save-draft.php', payload);
|
|
|
+
|
|
|
+ if (response.upload_errors && Object.keys(response.upload_errors).length > 0) {
|
|
|
+ showErrors(response.upload_errors);
|
|
|
+ showMessage('Einige Dateien konnten nicht gespeichert werden.');
|
|
|
+ } else if (showSavedText) {
|
|
|
+ showMessage('Entwurf gespeichert: ' + (response.updated_at || ''));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.uploads) {
|
|
|
+ renderUploadInfo(response.uploads);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (includeFiles) {
|
|
|
+ Array.from(applicationForm.querySelectorAll('input[type="file"]')).forEach((input) => {
|
|
|
+ input.value = '';
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function submitApplication() {
|
|
|
+ const payload = collectPayload(true);
|
|
|
+ const response = await postForm('/api/submit.php', payload);
|
|
|
+ clearErrors();
|
|
|
+ showMessage('Antrag erfolgreich abgeschlossen. Vielen Dank.');
|
|
|
+ submitBtn.disabled = true;
|
|
|
+ nextBtn.disabled = true;
|
|
|
+ prevBtn.disabled = true;
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+
|
|
|
+ startForm.addEventListener('submit', async (event) => {
|
|
|
+ event.preventDefault();
|
|
|
+
|
|
|
+ const emailInput = document.getElementById('startEmail');
|
|
|
+ const email = (emailInput.value || '').trim().toLowerCase();
|
|
|
+ if (!email) {
|
|
|
+ showMessage('Bitte E-Mail eingeben.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await loadDraft(email);
|
|
|
+ state.email = email;
|
|
|
+ applicationEmail.value = email;
|
|
|
+
|
|
|
+ if (result.already_submitted) {
|
|
|
+ wizardSection.classList.add('hidden');
|
|
|
+ blockedSection.classList.remove('hidden');
|
|
|
+ showMessage(result.message || 'Antrag bereits abgeschlossen.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ blockedSection.classList.add('hidden');
|
|
|
+ wizardSection.classList.remove('hidden');
|
|
|
+
|
|
|
+ fillFormData(result.data || {});
|
|
|
+ renderUploadInfo(result.uploads || {});
|
|
|
+
|
|
|
+ state.currentStep = Math.min(Math.max(Number(result.step || 1), 1), state.totalSteps);
|
|
|
+ updateProgress();
|
|
|
+
|
|
|
+ showMessage('Formular geladen. Entwurf wird automatisch gespeichert.');
|
|
|
+
|
|
|
+ if (state.autosaveId) {
|
|
|
+ clearInterval(state.autosaveId);
|
|
|
+ }
|
|
|
+ state.autosaveId = setInterval(async () => {
|
|
|
+ if (!state.email || wizardSection.classList.contains('hidden')) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await saveDraft(false, false);
|
|
|
+ } catch (_err) {
|
|
|
+ // Autsave errors are visible on next manual action.
|
|
|
+ }
|
|
|
+ }, 15000);
|
|
|
+ } catch (err) {
|
|
|
+ const msg = (err.payload && err.payload.message) || err.message || 'Laden fehlgeschlagen.';
|
|
|
+ showMessage(msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ prevBtn.addEventListener('click', async () => {
|
|
|
+ if (state.currentStep <= 1) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await saveDraft(false, true);
|
|
|
+ state.currentStep -= 1;
|
|
|
+ updateProgress();
|
|
|
+ } catch (err) {
|
|
|
+ const msg = (err.payload && err.payload.message) || err.message;
|
|
|
+ showMessage(msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ nextBtn.addEventListener('click', async () => {
|
|
|
+ if (state.currentStep >= state.totalSteps) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await saveDraft(false, true);
|
|
|
+ state.currentStep += 1;
|
|
|
+ updateProgress();
|
|
|
+ } catch (err) {
|
|
|
+ const msg = (err.payload && err.payload.message) || err.message;
|
|
|
+ showMessage(msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (uploadNowBtn) {
|
|
|
+ uploadNowBtn.addEventListener('click', async () => {
|
|
|
+ try {
|
|
|
+ await saveDraft(true, true);
|
|
|
+ } catch (err) {
|
|
|
+ const msg = (err.payload && err.payload.message) || err.message;
|
|
|
+ showMessage(msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ 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.';
|
|
|
+ showMessage(msg);
|
|
|
+ if (payload.already_submitted) {
|
|
|
+ blockedSection.classList.remove('hidden');
|
|
|
+ wizardSection.classList.add('hidden');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ updateProgress();
|
|
|
+})();
|