Implement marketing audit recommendations across site
- Add /services → /project-types 301 redirects in .htaccess - Remove .php extensions from all user-facing links across all pages - Rewrite homepage hero with benefit-focused H1, subtitle, CTAs, and stats - Add trust signals section below hero with badges - Rewrite service cards with benefit-focused copy - Update FAQ schema to match actual page FAQ content (6 questions) - Restructure quote form from 5-step to 3-step wizard with sidebar - Update about page mission statement and CTA copy - Update project-types page title, H1, and add Organization schema - Add trust signals and quote layout CSS styles - Clean sitemap: remove .php extensions, remove blog/search, update dates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
706
quote.php
706
quote.php
@@ -476,11 +476,11 @@ $breadcrumbs = [
|
||||
<div class="nav-menu" id="nav-menu">
|
||||
<a href="/" class="nav-link">Home</a>
|
||||
<a href="/#services" class="nav-link">Capabilities</a>
|
||||
<a href="project-types" class="nav-link">Project Types</a>
|
||||
<a href="about" class="nav-link">About</a>
|
||||
<a href="blog/" class="nav-link">Blog</a>
|
||||
<a href="/project-types" class="nav-link">Project Types</a>
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
<a href="/blog/" class="nav-link">Blog</a>
|
||||
<a href="/#contact" class="nav-link">Contact</a>
|
||||
<a href="quote" class="nav-link cta-button">Request Consultation</a>
|
||||
<a href="/quote" class="nav-link cta-button">Request Consultation</a>
|
||||
</div>
|
||||
<div class="nav-toggle" id="nav-toggle">
|
||||
<span class="bar"></span>
|
||||
@@ -502,185 +502,138 @@ $breadcrumbs = [
|
||||
<!-- Quote Form -->
|
||||
<section class="quote-form-section">
|
||||
<div class="container">
|
||||
<div class="quote-form-container">
|
||||
<!-- Message Container -->
|
||||
<div id="message-container" class="message-container">
|
||||
<div class="message-title"></div>
|
||||
<ul class="message-list"></ul>
|
||||
<div class="quote-page-layout">
|
||||
<!-- Main form -->
|
||||
<div class="quote-form-main">
|
||||
<div class="quote-form-container">
|
||||
<!-- Message Container -->
|
||||
<div id="message-container" class="message-container">
|
||||
<div class="message-title"></div>
|
||||
<ul class="message-list"></ul>
|
||||
</div>
|
||||
|
||||
<form id="quote-form" action="quote-handler.php" method="POST">
|
||||
<?php
|
||||
// Session already started at top of file
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
// Set form start time for bot detection
|
||||
$_SESSION['form_start_time'] = time();
|
||||
?>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<input type="hidden" name="recaptcha_response" id="recaptchaResponseQuote" value="">
|
||||
<input type="text" name="website" style="display: none !important; position: absolute !important; left: -9999px !important;" tabindex="-1" autocomplete="off">
|
||||
|
||||
<!-- STEP 1: Service & Scale -->
|
||||
<div class="form-step" id="step-1" data-step="1">
|
||||
<h2>Step 1: Tell us what you need</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="service_type">What service are you interested in?</label>
|
||||
<select name="service_type" id="service_type" required>
|
||||
<option value="">Select a service...</option>
|
||||
<option value="web-scraping">Web Scraping</option>
|
||||
<option value="data-cleaning">Data Cleaning</option>
|
||||
<option value="api-development">API Development</option>
|
||||
<option value="automation">Automation</option>
|
||||
<option value="custom">Custom Solution</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="scale">Estimated scale</label>
|
||||
<select name="scale" id="scale" required>
|
||||
<option value="">Select scale...</option>
|
||||
<option value="small">Small (under 1,000 records)</option>
|
||||
<option value="medium">Medium (1,000 - 50,000 records)</option>
|
||||
<option value="large">Large (50,000 - 500,000 records)</option>
|
||||
<option value="enterprise">Enterprise (500,000+ records)</option>
|
||||
<option value="unsure">Not sure yet</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary next-step">Next →</button>
|
||||
</div>
|
||||
|
||||
<!-- STEP 2: Contact & Timeline -->
|
||||
<div class="form-step" id="step-2" data-step="2" style="display:none;">
|
||||
<h2>Step 2: Your details</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="quote-name">Your name</label>
|
||||
<input type="text" name="name" id="quote-name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="quote-email">Email address</label>
|
||||
<input type="email" name="email" id="quote-email" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="quote-company">Company name</label>
|
||||
<input type="text" name="company" id="quote-company">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="timeline">When do you need this?</label>
|
||||
<select name="timeline" id="timeline" required>
|
||||
<option value="">Select timeline...</option>
|
||||
<option value="asap">As soon as possible</option>
|
||||
<option value="2weeks">Within 2 weeks</option>
|
||||
<option value="1month">Within a month</option>
|
||||
<option value="flexible">Flexible / No rush</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<button type="button" class="btn btn-secondary prev-step">← Back</button>
|
||||
<button type="button" class="btn btn-primary next-step">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEP 3: Project Details -->
|
||||
<div class="form-step" id="step-3" data-step="3" style="display:none;">
|
||||
<h2>Step 3: Project details</h2>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="website_urls">Target website(s)</label>
|
||||
<input type="text" name="data_sources" id="website_urls" placeholder="e.g., competitor.com, amazon.co.uk">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="quote-requirements">Detailed requirements <span class="optional">(optional)</span></label>
|
||||
<textarea name="requirements" id="quote-requirements" rows="5" placeholder="Tell us more about what data you need, how often, what format..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<button type="button" class="btn btn-secondary prev-step">← Back</button>
|
||||
<button type="submit" class="btn btn-primary">Get Your Free Proposal</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="quote-form" action="quote-handler.php" method="POST">
|
||||
<?php
|
||||
// Session already started at top of file
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
// Set form start time for bot detection
|
||||
$_SESSION['form_start_time'] = time();
|
||||
?>
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<!-- reCAPTCHA response field -->
|
||||
<input type="hidden" name="recaptcha_response" id="recaptchaResponseQuote" value="">
|
||||
<!-- Honeypot field for spam protection -->
|
||||
<input type="text" name="website" style="display: none !important; position: absolute !important; left: -9999px !important;" tabindex="-1" autocomplete="off">
|
||||
<!-- Step 1: Project Type -->
|
||||
<div class="form-step">
|
||||
<div class="step-title">
|
||||
<span class="step-number">1</span>
|
||||
What type of data services do you need? <span class="required-indicator">*</span>
|
||||
</div>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="services[]" value="web-scraping">
|
||||
<span>Web Scraping & Data Extraction</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="services[]" value="business-intelligence">
|
||||
<span>Business Intelligence & Analytics</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="services[]" value="data-processing">
|
||||
<span>Data Processing & Cleaning</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="services[]" value="automation">
|
||||
<span>Automation & APIs</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="services[]" value="consulting">
|
||||
<span>Custom Development</span>
|
||||
</label>
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" name="services[]" value="other">
|
||||
<span>Other (please specify below)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="quote-sidebar">
|
||||
<div class="testimonial-card">
|
||||
<blockquote>
|
||||
<p>"UK Data Services transformed how we track competitor pricing. The data is accurate, timely, and has directly impacted our revenue."</p>
|
||||
<cite>— Client Testimonial</cite>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Project Scale -->
|
||||
<div class="form-step">
|
||||
<div class="step-title">
|
||||
<span class="step-number">2</span>
|
||||
What's the scale of your project? <span class="required-indicator">*</span>
|
||||
</div>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="project_scale" value="small">
|
||||
<div>
|
||||
<strong>Small Project</strong><br>
|
||||
<small>One-time extraction, < 10k records</small>
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="project_scale" value="medium">
|
||||
<div>
|
||||
<strong>Medium Project</strong><br>
|
||||
<small>Regular updates, 10k-100k records</small>
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="project_scale" value="large">
|
||||
<div>
|
||||
<strong>Large Project</strong><br>
|
||||
<small>Ongoing service, 100k+ records</small>
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="project_scale" value="enterprise">
|
||||
<div>
|
||||
<strong>Enterprise</strong><br>
|
||||
<small>Complex multi-source solution</small>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="what-happens-next">
|
||||
<h3>What happens next?</h3>
|
||||
<ol>
|
||||
<li><strong>We review your request</strong> — our team assesses your requirements</li>
|
||||
<li><strong>You receive a detailed proposal</strong> — within 24 hours</li>
|
||||
<li><strong>Free consultation call</strong> — we schedule a call to discuss your project</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Timeline -->
|
||||
<div class="form-step">
|
||||
<div class="step-title">
|
||||
<span class="step-number">3</span>
|
||||
When do you need this completed? <span class="required-indicator">*</span>
|
||||
</div>
|
||||
<div class="radio-group">
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="timeline" value="asap">
|
||||
<span>ASAP (Rush job)</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="timeline" value="1-week">
|
||||
<span>Within 1 week</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="timeline" value="2-4-weeks">
|
||||
<span>2-4 weeks</span>
|
||||
</label>
|
||||
<label class="radio-item">
|
||||
<input type="radio" name="timeline" value="flexible">
|
||||
<span>Flexible timeline</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 4: Contact Information -->
|
||||
<div class="form-step">
|
||||
<div class="step-title">
|
||||
<span class="step-number">4</span>
|
||||
Your contact information
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="quote-name">Full Name *</label>
|
||||
<input type="text" id="quote-name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quote-email">Email Address *</label>
|
||||
<input type="email" id="quote-email" name="email" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="quote-company">Company</label>
|
||||
<input type="text" id="quote-company" name="company">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quote-phone">Phone Number</label>
|
||||
<input type="tel" id="quote-phone" name="phone">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 5: Project Details -->
|
||||
<div class="form-step">
|
||||
<div class="step-title">
|
||||
<span class="step-number">5</span>
|
||||
Project details
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quote-sources">Data Sources (websites, APIs, databases)</label>
|
||||
<textarea id="quote-sources" name="data_sources" rows="3" placeholder="Please list the websites, APIs, or data sources you need us to work with..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quote-requirements">Detailed Requirements *</label>
|
||||
<textarea id="quote-requirements" name="requirements" rows="5" required placeholder="Please describe your project in detail: what data you need, how you plan to use it, any specific format requirements, delivery preferences, etc."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quote-budget">Estimated Budget Range (Optional)</label>
|
||||
<select id="quote-budget" name="budget">
|
||||
<option value="">Select budget range...</option>
|
||||
<option value="under-1k">Under £1,000</option>
|
||||
<option value="1k-5k">£1,000 - £5,000</option>
|
||||
<option value="5k-10k">£5,000 - £10,000</option>
|
||||
<option value="10k-25k">£10,000 - £25,000</option>
|
||||
<option value="25k-50k">£25,000 - £50,000</option>
|
||||
<option value="50k-plus">£50,000+</option>
|
||||
<option value="discuss">Let's discuss</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<button type="submit" class="btn btn-primary btn-full">Get My Free Quote</button>
|
||||
</form>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -752,245 +705,188 @@ $breadcrumbs = [
|
||||
<!-- Scripts -->
|
||||
<script src="assets/js/main.js"></script>
|
||||
<script>
|
||||
// Enhanced quote form functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const checkboxItems = document.querySelectorAll('.checkbox-item');
|
||||
const radioItems = document.querySelectorAll('.radio-item');
|
||||
|
||||
// Handle checkbox styling
|
||||
checkboxItems.forEach(item => {
|
||||
const checkbox = item.querySelector('input[type="checkbox"]');
|
||||
checkbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
item.classList.add('checked');
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const steps = document.querySelectorAll('.form-step');
|
||||
const nextBtns = document.querySelectorAll('.next-step');
|
||||
const prevBtns = document.querySelectorAll('.prev-step');
|
||||
|
||||
nextBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const current = this.closest('.form-step');
|
||||
const currentStep = parseInt(current.dataset.step);
|
||||
const requiredFields = current.querySelectorAll('[required]');
|
||||
let valid = true;
|
||||
requiredFields.forEach(field => {
|
||||
if (!field.value) {
|
||||
field.classList.add('error');
|
||||
valid = false;
|
||||
} else {
|
||||
item.classList.remove('checked');
|
||||
field.classList.remove('error');
|
||||
}
|
||||
});
|
||||
if (!valid) return;
|
||||
|
||||
current.style.display = 'none';
|
||||
document.querySelector('[data-step="' + (currentStep + 1) + '"]').style.display = 'block';
|
||||
});
|
||||
|
||||
// Handle radio button styling
|
||||
radioItems.forEach(item => {
|
||||
const radio = item.querySelector('input[type="radio"]');
|
||||
radio.addEventListener('change', function() {
|
||||
// Remove checked class from all items in the same group
|
||||
const groupName = this.name;
|
||||
document.querySelectorAll(`input[name="${groupName}"]`).forEach(r => {
|
||||
r.closest('.radio-item').classList.remove('checked');
|
||||
});
|
||||
|
||||
// Add checked class to selected item
|
||||
if (this.checked) {
|
||||
item.classList.add('checked');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
prevBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const current = this.closest('.form-step');
|
||||
const currentStep = parseInt(current.dataset.step);
|
||||
current.style.display = 'none';
|
||||
document.querySelector('[data-step="' + (currentStep - 1) + '"]').style.display = 'block';
|
||||
});
|
||||
|
||||
// Message display functions
|
||||
function showMessage(type, title, messages) {
|
||||
const container = document.getElementById('message-container');
|
||||
const titleEl = container.querySelector('.message-title');
|
||||
const listEl = container.querySelector('.message-list');
|
||||
|
||||
// Reset classes
|
||||
container.className = 'message-container';
|
||||
container.classList.add(type);
|
||||
|
||||
// Set content
|
||||
titleEl.textContent = title;
|
||||
listEl.innerHTML = '';
|
||||
|
||||
if (Array.isArray(messages)) {
|
||||
messages.forEach(msg => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = msg;
|
||||
listEl.appendChild(li);
|
||||
});
|
||||
} else {
|
||||
});
|
||||
|
||||
// Message display functions
|
||||
function showMessage(type, title, messages) {
|
||||
const container = document.getElementById('message-container');
|
||||
const titleEl = container.querySelector('.message-title');
|
||||
const listEl = container.querySelector('.message-list');
|
||||
container.className = 'message-container';
|
||||
container.classList.add(type);
|
||||
titleEl.textContent = title;
|
||||
listEl.innerHTML = '';
|
||||
if (Array.isArray(messages)) {
|
||||
messages.forEach(msg => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = messages;
|
||||
li.textContent = msg;
|
||||
listEl.appendChild(li);
|
||||
}
|
||||
|
||||
// Show with animation
|
||||
container.classList.add('show');
|
||||
|
||||
// Scroll to message
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function hideMessage() {
|
||||
const container = document.getElementById('message-container');
|
||||
container.classList.remove('show');
|
||||
}
|
||||
|
||||
// Enhanced form submission with reCAPTCHA
|
||||
const quoteForm = document.getElementById('quote-form');
|
||||
|
||||
// Track form interactions
|
||||
let formInteractions = {
|
||||
mouseMovements: 0,
|
||||
keystrokes: 0,
|
||||
focusEvents: 0,
|
||||
startTime: Date.now(),
|
||||
fields: {}
|
||||
};
|
||||
|
||||
// Add hidden timestamp field
|
||||
const timestampField = document.createElement('input');
|
||||
timestampField.type = 'hidden';
|
||||
timestampField.name = 'form_timestamp';
|
||||
timestampField.value = Date.now();
|
||||
quoteForm.appendChild(timestampField);
|
||||
|
||||
// Add hidden interaction token
|
||||
const interactionToken = document.createElement('input');
|
||||
interactionToken.type = 'hidden';
|
||||
interactionToken.name = 'interaction_token';
|
||||
quoteForm.appendChild(interactionToken);
|
||||
|
||||
// Track mouse movements
|
||||
document.addEventListener('mousemove', function() {
|
||||
if (formInteractions.mouseMovements < 100) {
|
||||
formInteractions.mouseMovements++;
|
||||
}
|
||||
});
|
||||
|
||||
// Track form field interactions
|
||||
quoteForm.querySelectorAll('input, textarea, select').forEach(field => {
|
||||
field.addEventListener('keydown', function() {
|
||||
formInteractions.keystrokes++;
|
||||
});
|
||||
field.addEventListener('focus', function() {
|
||||
formInteractions.focusEvents++;
|
||||
});
|
||||
} else {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = messages;
|
||||
listEl.appendChild(li);
|
||||
}
|
||||
container.classList.add('show');
|
||||
container.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function hideMessage() {
|
||||
const container = document.getElementById('message-container');
|
||||
container.classList.remove('show');
|
||||
}
|
||||
|
||||
// Form submission with reCAPTCHA
|
||||
const quoteForm = document.getElementById('quote-form');
|
||||
|
||||
// Track form interactions for bot detection
|
||||
let formInteractions = {
|
||||
mouseMovements: 0,
|
||||
keystrokes: 0,
|
||||
focusEvents: 0,
|
||||
startTime: Date.now(),
|
||||
fields: {}
|
||||
};
|
||||
|
||||
const timestampField = document.createElement('input');
|
||||
timestampField.type = 'hidden';
|
||||
timestampField.name = 'form_timestamp';
|
||||
timestampField.value = Date.now();
|
||||
quoteForm.appendChild(timestampField);
|
||||
|
||||
const interactionToken = document.createElement('input');
|
||||
interactionToken.type = 'hidden';
|
||||
interactionToken.name = 'interaction_token';
|
||||
quoteForm.appendChild(interactionToken);
|
||||
|
||||
document.addEventListener('mousemove', function() {
|
||||
if (formInteractions.mouseMovements < 100) {
|
||||
formInteractions.mouseMovements++;
|
||||
}
|
||||
});
|
||||
|
||||
quoteForm.querySelectorAll('input, textarea, select').forEach(field => {
|
||||
field.addEventListener('keydown', function() {
|
||||
formInteractions.keystrokes++;
|
||||
});
|
||||
|
||||
quoteForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Calculate interaction score
|
||||
const timeSpent = Date.now() - formInteractions.startTime;
|
||||
const interactionScore = Math.min(100,
|
||||
(timeSpent > 5000 ? 20 : 0) +
|
||||
(formInteractions.mouseMovements > 10 ? 20 : 0) +
|
||||
(formInteractions.keystrokes > 20 ? 20 : 0) +
|
||||
(formInteractions.focusEvents > 3 ? 10 : 0)
|
||||
);
|
||||
|
||||
// Set interaction token
|
||||
interactionToken.value = btoa(JSON.stringify({
|
||||
score: interactionScore,
|
||||
time: timeSpent,
|
||||
interactions: formInteractions.mouseMovements + formInteractions.keystrokes + formInteractions.focusEvents
|
||||
}));
|
||||
|
||||
// Validate form
|
||||
const services = document.querySelectorAll('input[name="services[]"]:checked');
|
||||
const projectScale = document.querySelector('input[name="project_scale"]:checked');
|
||||
const timeline = document.querySelector('input[name="timeline"]:checked');
|
||||
const name = document.getElementById('quote-name').value.trim();
|
||||
const email = document.getElementById('quote-email').value.trim();
|
||||
const requirements = document.getElementById('quote-requirements').value.trim();
|
||||
|
||||
let errors = [];
|
||||
|
||||
if (services.length === 0) {
|
||||
errors.push('Please select at least one service');
|
||||
}
|
||||
|
||||
if (!projectScale) {
|
||||
errors.push('Please select a project scale');
|
||||
}
|
||||
|
||||
if (!timeline) {
|
||||
errors.push('Please select a timeline');
|
||||
}
|
||||
|
||||
if (!name || name.length < 2) {
|
||||
errors.push('Please enter your full name');
|
||||
}
|
||||
|
||||
if (!email || !email.includes('@')) {
|
||||
errors.push('Please enter a valid email address');
|
||||
}
|
||||
|
||||
if (!requirements || requirements.length < 20) {
|
||||
errors.push('Please provide detailed project requirements (minimum 20 characters)');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
showMessage('error', 'Please complete the following required fields:', errors);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide any existing error messages
|
||||
hideMessage();
|
||||
|
||||
// Execute reCAPTCHA
|
||||
const self = this;
|
||||
grecaptcha.ready(function() {
|
||||
grecaptcha.execute('<?php echo RECAPTCHA_SITE_KEY; ?>', {action: 'quote'}).then(function(token) {
|
||||
document.getElementById('recaptchaResponseQuote').value = token;
|
||||
|
||||
// Submit form
|
||||
const submitButton = self.querySelector('button[type="submit"]');
|
||||
const originalText = submitButton.textContent;
|
||||
submitButton.textContent = 'Sending Quote Request...';
|
||||
submitButton.disabled = true;
|
||||
|
||||
const formData = new FormData(self);
|
||||
|
||||
fetch('quote-handler.php', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showMessage('success', 'Thank you!', 'Your quote request has been sent successfully. We will get back to you within 24 hours with a detailed proposal.');
|
||||
self.reset();
|
||||
// Reset interaction tracking
|
||||
formInteractions = {
|
||||
mouseMovements: 0,
|
||||
keystrokes: 0,
|
||||
focusEvents: 0,
|
||||
startTime: Date.now(),
|
||||
fields: {}
|
||||
};
|
||||
// Reset styling
|
||||
checkboxItems.forEach(item => item.classList.remove('checked'));
|
||||
radioItems.forEach(item => item.classList.remove('checked'));
|
||||
|
||||
// Scroll to top after success
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
// Parse error message if it contains multiple validation errors
|
||||
let errorMessages = [];
|
||||
if (data.message.includes('. ')) {
|
||||
errorMessages = data.message.split('. ').filter(msg => msg.trim());
|
||||
} else {
|
||||
errorMessages = [data.message];
|
||||
}
|
||||
showMessage('error', 'There was a problem with your submission:', errorMessages);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showMessage('error', 'Connection Error', 'There was an error sending your quote request. Please check your internet connection and try again, or contact us directly at info@ukdataservices.co.uk');
|
||||
})
|
||||
.finally(() => {
|
||||
submitButton.textContent = originalText;
|
||||
submitButton.disabled = false;
|
||||
});
|
||||
field.addEventListener('focus', function() {
|
||||
formInteractions.focusEvents++;
|
||||
});
|
||||
});
|
||||
|
||||
quoteForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const timeSpent = Date.now() - formInteractions.startTime;
|
||||
const interactionScore = Math.min(100,
|
||||
(timeSpent > 5000 ? 20 : 0) +
|
||||
(formInteractions.mouseMovements > 10 ? 20 : 0) +
|
||||
(formInteractions.keystrokes > 20 ? 20 : 0) +
|
||||
(formInteractions.focusEvents > 3 ? 10 : 0)
|
||||
);
|
||||
|
||||
interactionToken.value = btoa(JSON.stringify({
|
||||
score: interactionScore,
|
||||
time: timeSpent,
|
||||
interactions: formInteractions.mouseMovements + formInteractions.keystrokes + formInteractions.focusEvents
|
||||
}));
|
||||
|
||||
const name = document.getElementById('quote-name').value.trim();
|
||||
const email = document.getElementById('quote-email').value.trim();
|
||||
let errors = [];
|
||||
|
||||
if (!name || name.length < 2) {
|
||||
errors.push('Please enter your name');
|
||||
}
|
||||
if (!email || !email.includes('@')) {
|
||||
errors.push('Please enter a valid email address');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
showMessage('error', 'Please complete the following:', errors);
|
||||
return;
|
||||
}
|
||||
|
||||
hideMessage();
|
||||
|
||||
const self = this;
|
||||
grecaptcha.ready(function() {
|
||||
grecaptcha.execute('<?php echo RECAPTCHA_SITE_KEY; ?>', {action: 'quote'}).then(function(token) {
|
||||
document.getElementById('recaptchaResponseQuote').value = token;
|
||||
|
||||
const submitButton = self.querySelector('button[type="submit"]');
|
||||
const originalText = submitButton.textContent;
|
||||
submitButton.textContent = 'Sending...';
|
||||
submitButton.disabled = true;
|
||||
|
||||
const formData = new FormData(self);
|
||||
|
||||
fetch('quote-handler.php', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showMessage('success', 'Thank you!', 'Your quote request has been sent successfully. We will get back to you within 24 hours with a detailed proposal.');
|
||||
self.reset();
|
||||
document.querySelectorAll('.form-step').forEach(s => s.style.display = 'none');
|
||||
document.querySelector('[data-step="1"]').style.display = 'block';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
let errorMessages = data.message.includes('. ') ? data.message.split('. ').filter(msg => msg.trim()) : [data.message];
|
||||
showMessage('error', 'There was a problem:', errorMessages);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showMessage('error', 'Connection Error', 'There was an error sending your request. Please try again or contact us at info@ukdataservices.co.uk');
|
||||
})
|
||||
.finally(() => {
|
||||
submitButton.textContent = originalText;
|
||||
submitButton.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user