81feccdc1a
- Remove old flat HTML pages (index, about, blog, contact, reviews, services/*, locations/*) - Remove Python/Flask API container (api/) - Remove old root nginx.conf and components/ - Add infra/: full nginx.conf (http block at /etc/nginx/nginx.conf), php-fpm-pool.conf (TCP listen), supervisord.conf, entrypoint.sh (auto-generates ALTCHA_HMAC_KEY) - Add src/: PHP router, page/service/location/blog templates, contact handler, altcha handler, promo endpoint, SQLite data files - Rewrite Dockerfile: single container, tini PID 1, healthcheck, all env vars declared - Update docker-compose.yml: port 8096, env_file, healthcheck - Update .dockerignore: exclude .env.*, include robots.txt/sitemap.xml/404.html/500.html - Update assets: tokens.css, promo-popup.css/js, altcha.min.js, refactored form.js/main.js Verified: all 17 routes 200, protection audit PASS, Resend confirmed working Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
4.5 KiB
JavaScript
116 lines
4.5 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
const form = document.getElementById('contactForm');
|
|
const formLoadedAtInput = document.getElementById('form_loaded_at');
|
|
const formStatusDiv = document.getElementById('formStatus');
|
|
|
|
// Set form_loaded_at to current timestamp in milliseconds
|
|
if (formLoadedAtInput) {
|
|
formLoadedAtInput.value = Date.now().toString();
|
|
}
|
|
|
|
// Initialize Altcha
|
|
const altchaElement = document.getElementById('altcha-widget');
|
|
if (altchaElement) {
|
|
window.altcha = new Altcha({
|
|
challengeUrl: '/altcha-challenge/',
|
|
element: altchaElement
|
|
});
|
|
}
|
|
|
|
// Form submit handler
|
|
if (form) {
|
|
form.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
|
|
// Clear previous status messages
|
|
formStatusDiv.innerHTML = '';
|
|
formStatusDiv.className = '';
|
|
|
|
// Validate required fields
|
|
const name = form.elements['name']?.value.trim();
|
|
const email = form.elements['email']?.value.trim();
|
|
|
|
if (!name || !email) {
|
|
formStatusDiv.className = 'form-status form-status--error';
|
|
formStatusDiv.innerHTML = '<p>Please fill in all required fields.</p>';
|
|
return;
|
|
}
|
|
|
|
if (!email.includes('@')) {
|
|
formStatusDiv.className = 'form-status form-status--error';
|
|
formStatusDiv.innerHTML = '<p>Please enter a valid email address.</p>';
|
|
return;
|
|
}
|
|
|
|
// Check honeypot
|
|
const honeypot = form.elements['website']?.value;
|
|
if (honeypot) {
|
|
formStatusDiv.className = 'form-status form-status--error';
|
|
formStatusDiv.innerHTML = '<p>Form validation failed.</p>';
|
|
return;
|
|
}
|
|
|
|
// Solve Altcha if available
|
|
let altchaPayload = '';
|
|
if (window.altcha && !window.altcha.didSubmit) {
|
|
try {
|
|
await window.altcha.solve();
|
|
altchaPayload = window.altcha.getFormData().altcha;
|
|
} catch (err) {
|
|
formStatusDiv.className = 'form-status form-status--error';
|
|
formStatusDiv.innerHTML = '<p>Spam check failed. Please try again.</p>';
|
|
return;
|
|
}
|
|
} else if (window.altcha) {
|
|
const formData = window.altcha.getFormData();
|
|
altchaPayload = formData.altcha || '';
|
|
}
|
|
|
|
// Build JSON payload
|
|
const payload = {
|
|
name: form.elements['name'].value.trim(),
|
|
email: form.elements['email'].value.trim(),
|
|
phone: form.elements['phone']?.value.trim() || '',
|
|
message: form.elements['message']?.value.trim() || '',
|
|
website: form.elements['website']?.value || '',
|
|
form_loaded_at: form.elements['form_loaded_at']?.value || '',
|
|
altcha: altchaPayload
|
|
};
|
|
|
|
// POST to /contact/
|
|
try {
|
|
const response = await fetch('/contact/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.ok) {
|
|
formStatusDiv.className = 'form-status form-status--success';
|
|
formStatusDiv.innerHTML = '<p>Thank you! Your message has been sent. We\'ll be in touch soon.</p>';
|
|
form.reset();
|
|
if (formLoadedAtInput) {
|
|
formLoadedAtInput.value = Date.now().toString();
|
|
}
|
|
if (window.altcha) {
|
|
window.altcha = new Altcha({
|
|
challengeUrl: '/altcha-challenge/',
|
|
element: document.getElementById('altcha-widget')
|
|
});
|
|
}
|
|
} else {
|
|
formStatusDiv.className = 'form-status form-status--error';
|
|
formStatusDiv.innerHTML = '<p>' + (data.error || 'An error occurred. Please try again.') + '</p>';
|
|
}
|
|
} catch (err) {
|
|
formStatusDiv.className = 'form-status form-status--error';
|
|
formStatusDiv.innerHTML = '<p>Network error. Please try again.</p>';
|
|
}
|
|
});
|
|
}
|
|
});
|