diff --git a/app/Http/Controllers/MarketingController.php b/app/Http/Controllers/MarketingController.php index f6a357f..e735b05 100644 --- a/app/Http/Controllers/MarketingController.php +++ b/app/Http/Controllers/MarketingController.php @@ -64,7 +64,6 @@ class MarketingController extends Controller 'name' => 'required|string|max:255', 'email' => 'required|email|max:255', 'message' => 'required|string|max:1000', - 'nickname' => 'present|size:0', ]); $locale = app()->getLocale(); diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 527cafd..a9d1e35 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -6,6 +6,7 @@ use App\Support\LocaleConfig; use Illuminate\Foundation\Inspiring; use Illuminate\Http\Request; use Inertia\Middleware; +use Spatie\Honeypot\Honeypot; class HandleInertiaRequests extends Middleware { @@ -67,6 +68,7 @@ class HandleInertiaRequests extends Middleware 'error' => fn () => $request->session()->get('error'), 'verification' => fn () => $request->session()->get('verification'), ], + 'honeypot' => fn () => new Honeypot(config('honeypot')), ]; } } diff --git a/composer.json b/composer.json index 4a311c9..28bc256 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "minishlink/web-push": "*", "sentry/sentry-laravel": "*", "simplesoftwareio/simple-qrcode": "^4.2", + "spatie/laravel-honeypot": "*", "spatie/laravel-translatable": "^6.11", "staudenmeir/belongs-to-through": "^2.17", "stripe/stripe-php": "*", diff --git a/composer.lock b/composer.lock index 8b969fa..5476f00 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5e1d60e650853d6113b01e1adaf49d65", + "content-hash": "a4956012b0e374c8f74b61a892e6b984", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -6804,6 +6804,82 @@ ], "time": "2024-05-17T09:06:10+00:00" }, + { + "name": "spatie/laravel-honeypot", + "version": "4.6.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-honeypot.git", + "reference": "62ec9dbecd2a17a4e2af62b09675f89813295cac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-honeypot/zipball/62ec9dbecd2a17a4e2af62b09675f89813295cac", + "reference": "62ec9dbecd2a17a4e2af62b09675f89813295cac", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0|^12.0", + "illuminate/encryption": "^11.0|^12.0", + "illuminate/http": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "illuminate/validation": "^11.0|^12.0", + "nesbot/carbon": "^2.0|^3.0", + "php": "^8.2", + "spatie/laravel-package-tools": "^1.9", + "symfony/http-foundation": "^7.0|^8.0" + }, + "require-dev": { + "livewire/livewire": "^3.0", + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest": "^2.0|^3.0|^4.0", + "pestphp/pest-plugin-livewire": "^1.0|^2.1|^3.0|^4.0", + "spatie/pest-plugin-snapshots": "^1.1|^2.1", + "spatie/phpunit-snapshot-assertions": "^4.2|^5.1", + "spatie/test-time": "^1.2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Honeypot\\HoneypotServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Honeypot\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Preventing spam submitted through forms", + "homepage": "https://github.com/spatie/laravel-honeypot", + "keywords": [ + "laravel-honeypot", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-honeypot/tree/4.6.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2025-11-28T09:57:48+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.92.7", diff --git a/resources/js/pages/marketing/Home.tsx b/resources/js/pages/marketing/Home.tsx index eeff141..b6ec9a9 100644 --- a/resources/js/pages/marketing/Home.tsx +++ b/resources/js/pages/marketing/Home.tsx @@ -25,6 +25,20 @@ interface Props { packages: Package[]; } +interface HoneypotPayload { + enabled: boolean; + nameFieldName: string; + validFromFieldName: string; + encryptedValidFrom: string; +} + +type ContactFormData = { + name: string; + email: string; + message: string; + [key: string]: string; +}; + const heroBulletIcons = [Sparkles, ShieldCheck, Camera]; const howStepIcons = [QrCode, Smartphone, ShieldCheck]; @@ -36,13 +50,24 @@ const Home: React.FC = ({ packages }) => { variant: heroCtaVariant, trackClick: trackHeroCtaClick, } = useCtaExperiment('home_hero_cta'); - const { flash } = usePage<{ flash?: { success?: string } }>().props; + const { flash, honeypot } = usePage<{ flash?: { success?: string }; honeypot?: HoneypotPayload }>().props; const shouldReduceMotion = useReducedMotion(); - const { data, setData, post, processing, errors, reset } = useForm({ + const honeypotDefaults = React.useMemo(() => { + if (!honeypot?.enabled) { + return {}; + } + + return { + [honeypot.nameFieldName]: '', + [honeypot.validFromFieldName]: honeypot.encryptedValidFrom, + }; + }, [honeypot?.enabled, honeypot?.encryptedValidFrom, honeypot?.nameFieldName, honeypot?.validFromFieldName]); + + const { data, setData, post, processing, errors, reset } = useForm({ name: '', email: '', message: '', - nickname: '', + ...honeypotDefaults, }); const viewportOnce = { once: true, amount: 0.25 }; @@ -619,91 +644,101 @@ const Home: React.FC = ({ packages }) => {
- setData('nickname', event.target.value)} - className="hidden" - tabIndex={-1} - autoComplete="off" - aria-hidden - /> -
-
- - setData('name', event.target.value)} - className="h-12 rounded-xl border-gray-200/70 bg-white/90 shadow-inner shadow-gray-200/40 focus-visible:ring-rose-300/60 dark:border-gray-700 dark:bg-gray-900/70" - autoComplete="name" - required - aria-invalid={Boolean(errors.name)} - aria-describedby={errors.name ? 'contact-name-error' : undefined} - /> - {errors.name && ( -

{errors.name}

- )} -
-
- - setData('email', event.target.value)} - className="h-12 rounded-xl border-gray-200/70 bg-white/90 shadow-inner shadow-gray-200/40 focus-visible:ring-rose-300/60 dark:border-gray-700 dark:bg-gray-900/70" - autoComplete="email" - required - aria-invalid={Boolean(errors.email)} - aria-describedby={errors.email ? 'contact-email-error' : undefined} - /> - {errors.email && ( -

{errors.email}

- )} -
-
-
- -