[Formeleon] Missing form ID or public key configuration.
'; + return; + } + + // Show loading state + container.innerHTML = 'Loading form...
'; + + // Step 1: Handshake (Fetch form config & reCAPTCHA config) + fetch(`${apiBaseUrl}/forms/${formId}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${publicKey}`, + 'Accept': 'application/json' + } + }) + .then(response => { + if (!response.ok) { + throw new Error(`Failed to load form (Status: ${response.status})`); + } + return response.json(); + }) + .then(data => { + renderForm(container, data); + }) + .catch(err => { + console.error('[Formeleon] Handshake Error:', err); + container.innerHTML = `[Formeleon Error] ${err.message}
`; + }); + } + + /** + * Helper to extract localized text based on the document lang + */ + function getLocalizedString(fieldProperty, fallback) { + if (!fieldProperty) return fallback || ''; + + // If it's a simple string, return it directly + if (typeof fieldProperty === 'string') return fieldProperty; + + // If it's an object with language keys (e.g., {"en": "Name", "de": "Name"}) + if (typeof fieldProperty === 'object') { + const pageLang = document.documentElement.lang || 'en'; + // Try exact match (e.g. 'en-US') + if (fieldProperty[pageLang]) return fieldProperty[pageLang]; + // Try base language match (e.g. 'en') + const baseLang = pageLang.split('-')[0]; + if (fieldProperty[baseLang]) return fieldProperty[baseLang]; + // Fallback to English if available, or just take the first available value + if (fieldProperty['en']) return fieldProperty['en']; + return Object.values(fieldProperty)[0] || fallback; + } + + return fallback || ''; + } + + /** + * Renders the HTML form based on JSON structure + */ + function renderForm(container, formData) { + if (!formData || !formData.form_structure) { + container.innerHTML = 'Invalid form configuration received.
'; + return; + } + + // Load reCAPTCHA script if enabled + if (formData.recaptcha && formData.recaptcha.enabled && formData.recaptcha.site_key) { + window._formeleonRecaptchaSiteKey = formData.recaptcha.site_key; + const rcScript = document.createElement('script'); + rcScript.src = `https://www.google.com/recaptcha/api.js?render=${formData.recaptcha.site_key}`; + document.head.appendChild(rcScript); + } + + const struct = formData.form_structure; + const fields = struct.fields || []; + + let html = ``; + + container.innerHTML = html; + + // Attach Submit Handler + const formEl = document.getElementById('formeleon-submit-form'); + formEl.addEventListener('submit', handleFormSubmit); + } + + /** + * Handles the form submission process + */ + function handleFormSubmit(e) { + e.preventDefault(); + + const formEl = e.target; + const btn = document.getElementById('formeleon-submit-btn'); + const alertBox = document.getElementById('formeleon-alert'); + + // Hide previous alerts + alertBox.style.display = 'none'; + + // Extract Data + const formData = new FormData(formEl); + const payload = {}; + formData.forEach((value, key) => { + payload[key] = value; + }); + + // Update UI State + btn.disabled = true; + btn.innerText = 'Submitting...'; + + // Helper to send data + const sendPayload = (recaptchaToken) => { + fetch(`${apiBaseUrl}/submissions`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${publicKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + form_id: formId, + payload: payload, + recaptcha_token: recaptchaToken + }) + }) + .then(response => { + return response.json().then(data => ({status: response.status, body: data})); + }) + .then(res => { + if (res.status >= 200 && res.status < 300) { + // Success + alertBox.style.display = 'block'; + alertBox.style.backgroundColor = '#d4edda'; + alertBox.style.color = '#155724'; + alertBox.style.border = '1px solid #c3e6cb'; + alertBox.innerHTML = res.body.message || 'Thank you! Your submission has been received.'; + formEl.reset(); + } else { + // Error + throw new Error(res.body.error || 'Submission failed.'); + } + }) + .catch(err => { + // Error Display + alertBox.style.display = 'block'; + alertBox.style.backgroundColor = '#f8d7da'; + alertBox.style.color = '#721c24'; + alertBox.style.border = '1px solid #f5c6cb'; + alertBox.innerHTML = err.message; + }) + .finally(() => { + btn.disabled = false; + btn.innerText = 'Submit'; + }); + }; + + // Execute reCAPTCHA if enabled + if (window._formeleonRecaptchaSiteKey && typeof grecaptcha !== 'undefined') { + grecaptcha.ready(function() { + grecaptcha.execute(window._formeleonRecaptchaSiteKey, {action: 'submit'}).then(function(token) { + sendPayload(token); + }).catch(function(err) { + console.error('[Formeleon] reCAPTCHA execution failed', err); + sendPayload(null); // Send anyway, backend will evaluate + }); + }); + } else { + // Dev mode or not configured + sendPayload('mock_token_for_dev'); + } + } + +})(); diff --git a/templates/admin/dashboard.html b/templates/admin/dashboard.html index e67b9d4..35a02f1 100644 --- a/templates/admin/dashboard.html +++ b/templates/admin/dashboard.html @@ -21,45 +21,51 @@ {# Statistics Cards #}| ID | Form | +Tenant | Date | Status | -Actions | +Actions | ||
|---|---|---|---|---|---|---|---|---|
| {{ submission.id[:12] }}... | -{{ submission.form_config.name if submission.form_config else 'N/A' }} | +{{ submission.id[:12] }}... | +{{ submission.form_config.name if submission.form_config else 'N/A' }} | +{{ submission.tenant.name if submission.tenant else 'N/A' }} | {{ submission.created_at.strftime('%Y-%m-%d %H:%M') if submission.created_at else 'N/A' }} | - - {{ submission.status.value }} - + {% if submission.status.value == 'processed' %} + Processed + {% elif submission.status.value == 'pending' %} + Pending + {% elif submission.status.value == 'spam' %} + Spam + {% else %} + {{ submission.status.value }} + {% endif %} | -- - + | + + View |
No recent submissions
+No active recent submissions found.