Add honeypot, spam protection to contact form
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import SuccessModal from '$lib/components/SuccessModal.svelte';
|
||||
import ErrorModal from '$lib/components/ErrorModal.svelte';
|
||||
@@ -17,6 +18,8 @@
|
||||
let location = '';
|
||||
let message = '';
|
||||
let selectedServices: string[] = [];
|
||||
let website = '';
|
||||
let formStartedAt = 0;
|
||||
|
||||
let fullNameInput: HTMLInputElement;
|
||||
let emailInput: HTMLInputElement;
|
||||
@@ -63,6 +66,10 @@
|
||||
$: ownerStepLabel = booking.ownerStepLabel?.trim() || 'Owner Details';
|
||||
$: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog';
|
||||
|
||||
onMount(() => {
|
||||
formStartedAt = Date.now();
|
||||
});
|
||||
|
||||
function splitBookingTitle(title: string) {
|
||||
const trimmed = title.trim();
|
||||
const lastSpace = trimmed.lastIndexOf(' ');
|
||||
@@ -147,6 +154,8 @@
|
||||
body: JSON.stringify({
|
||||
fullName, email, phone, petName, location, message,
|
||||
services: selectedServices,
|
||||
website,
|
||||
formStartedAt,
|
||||
referrer: document.referrer,
|
||||
page: window.location.href,
|
||||
}),
|
||||
@@ -224,6 +233,18 @@
|
||||
novalidate
|
||||
on:submit={handleSubmit}
|
||||
>
|
||||
<div class="booking-honeypot" aria-hidden="true">
|
||||
<label for="website">Website</label>
|
||||
<input
|
||||
bind:value={website}
|
||||
type="text"
|
||||
id="website"
|
||||
name="website"
|
||||
tabindex="-1"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if step === 1}
|
||||
<div class="booking-panel">
|
||||
{#if hasBanner}
|
||||
@@ -443,4 +464,15 @@
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.booking-honeypot {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -101,8 +101,10 @@ describe('BookingSection', () => {
|
||||
location: 'Kingsland',
|
||||
message: 'Loves small group walks.',
|
||||
services: ['Pack Walks', 'Other Services'],
|
||||
website: '',
|
||||
referrer: 'https://www.google.com/'
|
||||
});
|
||||
expect(payload.formStartedAt).toEqual(expect.any(Number));
|
||||
|
||||
expect(screen.getByRole('dialog', { name: /Booking confirmed/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /on our radar/i })).toBeInTheDocument();
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<div class="footer-action">
|
||||
<p class="footer-col-label">Get Started</p>
|
||||
<a href="/booking" class="footer-book-btn">
|
||||
<a href="/contact-us" class="footer-book-btn">
|
||||
Book a Meet & Greet
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { reveal } from '$lib/actions/reveal';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import type { IconCard } from '$lib/types';
|
||||
|
||||
@@ -6,7 +7,7 @@
|
||||
|
||||
</script>
|
||||
|
||||
<section id="services">
|
||||
<section id="services" use:reveal={{ delay: 20 }} class="reveal-block">
|
||||
<div class="services-inner">
|
||||
<h2 class="section-heading">What we do</h2>
|
||||
|
||||
@@ -27,3 +28,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
@media (hover: hover) and (min-width: 769px) {
|
||||
:global(.reveal-visible.reveal-block) .service-card {
|
||||
animation: service-card-settle 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) .service-card:nth-child(1) {
|
||||
animation-delay: 0.02s;
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) .service-card:nth-child(2) {
|
||||
animation-delay: 0.06s;
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) .service-card:nth-child(3) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes service-card-settle {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user