feat(contact): Add hCaptcha integration, Discord alert for contact

- Added hCaptcha validation to the contact form.
- Integrated hCaptcha configuration in the application.
- Implemented Discord alerts for new contact submissions with detailed content.
This commit is contained in:
Rico van Zelst
2024-02-24 21:52:00 +01:00
parent 1ed7856985
commit 8bdcd5b086
6 changed files with 131 additions and 38 deletions

View File

@@ -20,6 +20,9 @@ GTAG_MEASUREMENT_ID="G-XXXXXXXXXX"
DISCORD_ALERT_WEBHOOK="https://discord.com/api/webhooks/000000000000000000/" DISCORD_ALERT_WEBHOOK="https://discord.com/api/webhooks/000000000000000000/"
HONEYPOT_NAME="honeypot" HONEYPOT_NAME="honeypot"
HCAPTCHA_SECRET=secret-key
HCAPTCHA_SITEKEY=site-key
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Http\Requests\ContactSubmissionRequest; use App\Http\Requests\ContactSubmissionRequest;
use App\Models\ContactSubmission; use App\Models\ContactSubmission;
use Spatie\DiscordAlerts\Facades\DiscordAlert;
class ContactSubmissionController extends Controller class ContactSubmissionController extends Controller
{ {
@@ -16,6 +17,20 @@ class ContactSubmissionController extends Controller
{ {
$contactSubmission = ContactSubmission::create($request->validated()); $contactSubmission = ContactSubmission::create($request->validated());
$descriptionContent = "**Name**: {$contactSubmission->name}\n\n**Email**: {$contactSubmission->email}\n\n**Category**: {$contactSubmission->category->humanReadable()}\n\n**Subject**: {$contactSubmission->subject}\n\n**Message**: {$contactSubmission->message}";
if ($contactSubmission->discord) {
$descriptionContent .= "\n\n\n**Discord**: {$contactSubmission->discord}";
}
DiscordAlert::message("There is a new contact submission from {$contactSubmission->name} ({$contactSubmission->email}).", [
[
'title' => "{$contactSubmission->category->humanReadable()} - {$contactSubmission->subject}",
'description' => $descriptionContent,
'color' => '#ff8a4c',
]
]);
return redirect()->route('contact.index')->with('success', 'Your message has been sent!'); return redirect()->route('contact.index')->with('success', 'Your message has been sent!');
} }
} }

View File

@@ -11,10 +11,11 @@ class ContactSubmissionRequest extends FormRequest
return [ return [
'name' => ['required', 'max:254'], 'name' => ['required', 'max:254'],
'email' => ['required', 'email', 'max:254'], 'email' => ['required', 'email', 'max:254'],
'discord' => ['nullable', 'min:2', 'max:34'], 'discord' => ['nullable', 'min:2', 'max:35'],
'category' => ['required', 'in:question,advertising,bug_report,feedback,other'], 'category' => ['required', 'in:question,advertising,bug_report,feedback,other'],
'subject' => ['required', 'max:254'], 'subject' => ['required', 'max:254'],
'message' => ['required', 'unique:contact_submissions', 'max:3500'], 'message' => ['required', 'unique:contact_submissions', 'max:3500'],
'h-captcha-response' => 'required|HCaptcha'
]; ];
} }
@@ -22,4 +23,14 @@ class ContactSubmissionRequest extends FormRequest
{ {
return true; return true;
} }
public function messages(): array
{
return [
'h-captcha-response.required' => 'Please verify that you are not a robot.',
'h-captcha-response.h_captcha' => 'Failed to validate captcha.',
'category.in' => 'Invalid category.',
'message.unique' => 'You have already submitted this message.',
];
}
} }

10
config/HCaptcha.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
return [
'secret' => env('HCAPTCHA_SECRET'),
'sitekey' => env('HCAPTCHA_SITEKEY'),
'server-get-config' => false,
'options' => [
'timeout' => 30,
],
];

View File

@@ -170,6 +170,7 @@ return [
// App\Providers\BroadcastServiceProvider::class, // App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
Scyllaly\HCaptcha\HCaptchaServiceProvider::class,
])->toArray(), ])->toArray(),
/* /*
@@ -184,6 +185,7 @@ return [
*/ */
'aliases' => Facade::defaultAliases()->merge([ 'aliases' => Facade::defaultAliases()->merge([
'HCaptcha' => Scyllaly\HCaptcha\Facades\HCaptcha::class,
// 'Example' => App\Facades\Example::class, // 'Example' => App\Facades\Example::class,
])->toArray(), ])->toArray(),

View File

@@ -1,44 +1,96 @@
@extends('layouts.app') @extends('layouts.app')
@section('title', 'Heimerdinger.LoL • Contact') @section('title', 'Heimerdinger.LoL • Contact')
@section('description', 'Contact Heimerdinger.LoL for any inquiries, feedback, or suggestions. We are always looking to @section('description',
improve our website and content.') 'Contact Heimerdinger.LoL for any inquiries, feedback, or suggestions. We are always looking to
improve our website and content.')
@push('top_scripts')
{!! HCaptcha::renderJs() !!}
@endpush
@section('content') @section('content')
<form method="POST" action="{{route('contact.store')}}" class="max-w-screen-md mx-auto mt-2 prose prose-stone"> <div class="mt-8 mb-24">
@csrf <h1
@honeypot class="text-3xl font-bold text-center text-transparent uppercase sm:text-4xl bg-gradient-to-bl from-orange-300 to-orange-500 bg-clip-text">
<div class="mb-6"> Get in Contact</h1>
<label for="name" class="block mb-2 text-sm font-bold text-stone-300">Name</label> <p class="mb-2 text-sm italic text-center text-stone-400">* = required</p>
<input type="text" id="name" name="name" class="w-full p-2 border border-stone-300 rounded">
</div>
<div class="mb-6">
<label for="email" class="block mb-2 text-sm font-bold text-stone-300">Email</label>
<input type="email" id="email" name="email" class="w-full p-2 border border-stone-300 rounded">
</div>
<div class="mb-6">
<label for="discord" class="block mb-2 text-sm font-bold text-stone-300">Discord (optional)</label>
<input type="text" id="discord" name="discord" class="w-full p-2 border border-stone-300 rounded">
</div>
<div class="mb-6">
<label for="category" class="block mb-2 text-sm font-bold text-stone-300">Category</label>
<select id="category" name="category" class="w-full p-2 border border-stone-300 rounded">
<option value="question">Question</option>
<option value="advertising">Advertising</option>
<option value="bug_report">Bug Report</option>
<option value="feedback">Feedback</option>
<option value="other">Other</option>
</select>
</div>
<div class="mb-6">
<label for="subject" class="block mb-2 text-sm font-bold text-stone-300">Subject</label>
<input type="text" id="subject" name="subject" class="w-full p-2 border border-stone-300 rounded">
</div>
<div class="mb-6">
<label for="message" class="block mb-2 text-sm font-bold text-stone-300">Message</label>
<textarea id="message" name="message" class="w-full p-2 border border-stone-300 rounded"></textarea>
</div>
<button type="submit" class="w-full p-3 bg-orange-500 text-stone-900 rounded hover:bg-orange-400">Submit</button>
</form> @if ($errors->any())
<div class="max-w-screen-md mx-auto mt-8 mb-8 prose prose-stone">
<div class="p-4 text-red-700 bg-red-100 border-l-4 border-red-500">
<p class="font-bold">There was an error with your submission</p>
<ul class="list-disc list-inside">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
@endif
@if (session('success'))
<div class="max-w-screen-md mx-auto mt-8 mb-8 prose prose-stone">
<div class="p-4 text-green-700 bg-green-100 border-l-4 border-green-500">
<p class="font-bold">Success</p>
<p>{{ session('success') }}</p>
</div>
</div>
@endif
<form method="POST" action="{{ route('contact.store') }}" class="max-w-screen-md mx-auto mt-2 prose prose-stone">
@csrf
@honeypot
<div class="mb-6">
<label for="name" class="block mb-2 text-sm font-bold text-stone-300">Name <span
class="text-red-500">*</span></label>
<input required type="text" id="name" name="name" maxlength="50"
class="w-full p-2 border-2 rounded border-stone-600 bg-stone-700 focus:outline-none focus:border-stone-500 text-stone-300">
</div>
<div class="mb-6">
<label for="email" class="block mb-2 text-sm font-bold text-stone-300">Email <span
class="text-red-500">*</span></label>
<input required type="email" id="email" name="email" maxlength="250"
class="w-full p-2 border-2 rounded border-stone-600 bg-stone-700 focus:outline-none focus:border-stone-500 text-stone-300">
</div>
<div class="mb-6">
<label for="discord" class="block mb-2 text-sm font-bold text-stone-300">Discord <span
class="text-sm italic font-normal opacity-75">(optional)</span></label>
<input type="text" id="discord" name="discord" maxlength="34"
class="w-full p-2 border-2 rounded border-stone-600 bg-stone-700 focus:outline-none focus:border-stone-500 text-stone-300">
</div>
<div class="mb-6">
<label for="category" class="block mb-2 text-sm font-bold text-stone-300">Category <span
class="text-red-500">*</span></label>
<select id="category" name="category"
class="w-full p-2 border-2 rounded border-stone-600 bg-stone-700 focus:outline-none focus:border-stone-500 text-stone-300">
<option value="question">Question</option>
<option value="advertising">Advertising</option>
<option value="bug_report">Bug Report</option>
<option value="feedback">Feedback</option>
<option value="other">Other</option>
</select>
</div>
<div class="mb-6">
<label for="subject" class="block mb-2 text-sm font-bold text-stone-300">Subject <span
class="text-red-500">*</span></label>
<input required type="text" id="subject" name="subject" maxlength="200"
class="w-full p-2 border-2 rounded border-stone-600 bg-stone-700 focus:outline-none focus:border-stone-500 text-stone-300">
</div>
<div class="mb-6">
<label for="message" class="block mb-2 text-sm font-bold text-stone-300">Message <span
class="text-red-500">*</span></label>
<textarea required rows="4" id="message" name="message" maxlength="3200"
class="w-full p-2 border-2 rounded border-stone-600 bg-stone-700 focus:outline-none focus:border-stone-500 text-stone-300"></textarea>
</div>
<div class="mb-6">
<label for="h-captcha-response" class="block mb-2 text-sm font-bold text-stone-300">
Verify you are human <span class="text-red-500">*</span>
</label>
{!! HCaptcha::display(['data-theme' => 'dark']) !!}
</div>
<button type="submit"
class="w-full p-3 bg-orange-500 rounded text-stone-900 hover:bg-orange-400">Submit</button>
</form>
</div>
@endsection @endsection