Add address and sqft fields, fix altcha web component render
This commit is contained in:
+16
-43
@@ -8,29 +8,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
formLoadedAtInput.value = Date.now().toString();
|
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
|
// Form submit handler
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', async function(e) {
|
form.addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Clear previous status messages
|
|
||||||
formStatusDiv.innerHTML = '';
|
formStatusDiv.innerHTML = '';
|
||||||
formStatusDiv.className = '';
|
formStatusDiv.className = '';
|
||||||
|
|
||||||
// Validate required fields
|
const name = form.elements['name']?.value.trim();
|
||||||
const name = form.elements['name']?.value.trim();
|
const email = form.elements['email']?.value.trim();
|
||||||
const email = form.elements['email']?.value.trim();
|
const address = form.elements['address']?.value.trim();
|
||||||
|
|
||||||
if (!name || !email) {
|
if (!name || !email || !address) {
|
||||||
formStatusDiv.className = 'form-status form-status--error';
|
formStatusDiv.className = 'form-status form-status--error';
|
||||||
formStatusDiv.innerHTML = '<p>Please fill in all required fields.</p>';
|
formStatusDiv.innerHTML = '<p>Please fill in all required fields.</p>';
|
||||||
return;
|
return;
|
||||||
@@ -42,39 +32,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check honeypot
|
if (form.elements['website']?.value) return;
|
||||||
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
|
// Get altcha value from the web component
|
||||||
let altchaPayload = '';
|
const altchaWidget = document.getElementById('altcha-widget');
|
||||||
if (window.altcha && !window.altcha.didSubmit) {
|
const altchaPayload = altchaWidget ? (altchaWidget.value || '') : '';
|
||||||
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 = {
|
const payload = {
|
||||||
name: form.elements['name'].value.trim(),
|
name: name,
|
||||||
email: form.elements['email'].value.trim(),
|
email: email,
|
||||||
phone: form.elements['phone']?.value.trim() || '',
|
phone: form.elements['phone']?.value.trim() || '',
|
||||||
message: form.elements['message']?.value.trim() || '',
|
address: address,
|
||||||
website: form.elements['website']?.value || '',
|
sqft: form.elements['sqft']?.value.trim() || '',
|
||||||
|
message: form.elements['message']?.value.trim() || '',
|
||||||
|
website: form.elements['website']?.value || '',
|
||||||
form_loaded_at: form.elements['form_loaded_at']?.value || '',
|
form_loaded_at: form.elements['form_loaded_at']?.value || '',
|
||||||
altcha: altchaPayload
|
altcha: altchaPayload
|
||||||
};
|
};
|
||||||
|
|
||||||
// POST to /contact/
|
// POST to /contact/
|
||||||
|
|||||||
+18
-12
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Floor It Hardwood Floors — contact form handler.
|
* Floor It Hardwood Floors: contact form handler.
|
||||||
*
|
*
|
||||||
* Pipeline:
|
* Pipeline:
|
||||||
* 1. Read JSON body (32KB cap)
|
* 1. Read JSON body (32KB cap)
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
* 3. Honeypot + time-on-page checks
|
* 3. Honeypot + time-on-page checks
|
||||||
* 4. Altcha server-side verify
|
* 4. Altcha server-side verify
|
||||||
* 5. Sliding-window per-IP rate limit (file-backed in /var/www/html/src/api/data/rate-limits/)
|
* 5. Sliding-window per-IP rate limit (file-backed in /var/www/html/src/api/data/rate-limits/)
|
||||||
* 6. POST to Resend → email to contact address
|
* 6. POST to Resend, email to contact address
|
||||||
* 7. JSON response
|
* 7. JSON response
|
||||||
*
|
*
|
||||||
* Configuration is read entirely from environment variables — set these in
|
* Configuration is read entirely from environment variables. Set these in
|
||||||
* .env or the runtime environment. No hardcoded keys in this file.
|
* .env or the runtime environment. No hardcoded keys in this file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -115,19 +115,23 @@ if (!rate_limit_check($ip, $RATE_LIMIT, 600)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Field extraction + validation ──────────────────────────────────
|
// ─── Field extraction + validation ──────────────────────────────────
|
||||||
$name = trim((string)($body['name'] ?? ''));
|
$name = trim((string)($body['name'] ?? ''));
|
||||||
$email = trim((string)($body['email'] ?? ''));
|
$email = trim((string)($body['email'] ?? ''));
|
||||||
$phone = trim((string)($body['phone'] ?? ''));
|
$phone = trim((string)($body['phone'] ?? ''));
|
||||||
|
$address = trim((string)($body['address'] ?? ''));
|
||||||
|
$sqft = trim((string)($body['sqft'] ?? ''));
|
||||||
$message = trim((string)($body['message'] ?? ''));
|
$message = trim((string)($body['message'] ?? ''));
|
||||||
$website = trim((string)($body['website'] ?? '')); // honeypot
|
$website = trim((string)($body['website'] ?? '')); // honeypot
|
||||||
$form_loaded_at = trim((string)($body['form_loaded_at'] ?? ''));
|
$form_loaded_at = trim((string)($body['form_loaded_at'] ?? ''));
|
||||||
$altcha_payload = trim((string)($body['altcha'] ?? ''));
|
$altcha_payload = trim((string)($body['altcha'] ?? ''));
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
if (mb_strlen($name) < 2 || mb_strlen($name) > 80) $errors[] = 'name';
|
if (mb_strlen($name) < 2 || mb_strlen($name) > 80) $errors[] = 'name';
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'email';
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'email';
|
||||||
if (mb_strlen($phone) > 20) $errors[] = 'phone';
|
if (mb_strlen($phone) > 20) $errors[] = 'phone';
|
||||||
if (mb_strlen($message) > 2000) $errors[] = 'message';
|
if (mb_strlen($address) < 5 || mb_strlen($address) > 200) $errors[] = 'address';
|
||||||
|
if ($sqft !== '' && (!ctype_digit($sqft) || (int)$sqft > 99999)) $errors[] = 'sqft';
|
||||||
|
if (mb_strlen($message) > 2000) $errors[] = 'message';
|
||||||
|
|
||||||
if ($errors) {
|
if ($errors) {
|
||||||
error_log("[floorit.form] validation_error request_id=$request_id fields=" . implode(',', $errors));
|
error_log("[floorit.form] validation_error request_id=$request_id fields=" . implode(',', $errors));
|
||||||
@@ -162,7 +166,9 @@ $text_body =
|
|||||||
"A new estimate request came in through floorithardwoods.com.\n\n" .
|
"A new estimate request came in through floorithardwoods.com.\n\n" .
|
||||||
"Name: {$name}\n" .
|
"Name: {$name}\n" .
|
||||||
"Email: {$email}\n" .
|
"Email: {$email}\n" .
|
||||||
"Phone: " . ($phone ?: 'not provided') . "\n\n" .
|
"Phone: " . ($phone ?: 'not provided') . "\n" .
|
||||||
|
"Address: {$address}\n" .
|
||||||
|
"Sq Ft: " . ($sqft ?: 'not provided') . "\n\n" .
|
||||||
"Message:\n" . ($message ?: '(no message)') . "\n\n" .
|
"Message:\n" . ($message ?: '(no message)') . "\n\n" .
|
||||||
"Submitted at: " . gmdate('Y-m-d\TH:i:s\Z') . "\n" .
|
"Submitted at: " . gmdate('Y-m-d\TH:i:s\Z') . "\n" .
|
||||||
"Request id: {$request_id}\n";
|
"Request id: {$request_id}\n";
|
||||||
|
|||||||
@@ -241,12 +241,20 @@ foreach ($sections as $s) {
|
|||||||
<input type="tel" id="phone" name="phone" autocomplete="tel" placeholder="(716) 555-0000">
|
<input type="tel" id="phone" name="phone" autocomplete="tel" placeholder="(716) 555-0000">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="message">Message <span aria-hidden="true">*</span></label>
|
<label for="address">Property Address <span aria-hidden="true">*</span></label>
|
||||||
<textarea id="message" name="message" required rows="5" placeholder="Tell us about your floors and what you need..."></textarea>
|
<input type="text" id="address" name="address" required autocomplete="street-address" placeholder="123 Main St, Buffalo, NY">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sqft">Approximate Square Footage</label>
|
||||||
|
<input type="number" id="sqft" name="sqft" min="0" max="99999" placeholder="e.g. 800">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="message">Message</label>
|
||||||
|
<textarea id="message" name="message" rows="4" placeholder="Tell us about your project..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="website" style="display:none;" tabindex="-1" autocomplete="off">
|
<input type="text" name="website" style="display:none;" tabindex="-1" autocomplete="off">
|
||||||
<input type="hidden" name="form_loaded_at" id="form_loaded_at">
|
<input type="hidden" name="form_loaded_at" id="form_loaded_at">
|
||||||
<div id="altcha-widget"></div>
|
<altcha-widget id="altcha-widget" challengeurl="/altcha-challenge/"></altcha-widget>
|
||||||
<button type="submit" id="contactFormSubmit" class="btn btn--primary btn--full">Send Message</button>
|
<button type="submit" id="contactFormSubmit" class="btn btn--primary btn--full">Send Message</button>
|
||||||
<div id="formStatus" role="status" aria-live="polite"></div>
|
<div id="formStatus" role="status" aria-live="polite"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user