Das markemacht.de-Anfrageformular (Audit-Modal) war nach der Laravel- Integration ohne Endpoint. Es wird jetzt serverseitig über Livewire verarbeitet statt über das alte statische PHP-Skript. - AuditAnfrage-Mailable + Mail-View, Reply-To = Absender - config/marketing.php: Empfänger via MARKETING_ANFRAGE_TO (Default info@markemacht.de) - Livewire-SFC web.audit-form: Validierung (dt.), Honeypot, Rate-Limit (3/10 Min), dispatch('audit-sent') schließt das Modal - Modal-Markup auf <livewire:web.audit-form /> umgestellt - Feature-Tests (Validierung, Versand, Honeypot, Throttle) - Entwicklungsplan unter dev/audit-formular-cookies/ Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
140 lines
5.1 KiB
PHP
140 lines
5.1 KiB
PHP
<?php
|
|
|
|
use App\Mail\AuditAnfrage;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Livewire\Component;
|
|
|
|
new class extends Component
|
|
{
|
|
public string $name = '';
|
|
|
|
public string $email = '';
|
|
|
|
public string $message = '';
|
|
|
|
public bool $privacy = false;
|
|
|
|
/** Honeypot-Felder — für Menschen unsichtbar, von Bots befüllt. */
|
|
public string $company = '';
|
|
|
|
public string $url = '';
|
|
|
|
/** Statusmeldung für aria-live, sichtbar nach Absenden. */
|
|
public string $status = '';
|
|
|
|
public bool $sent = false;
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function rules(): array
|
|
{
|
|
return [
|
|
'name' => 'required|string|max:120',
|
|
'email' => 'required|email|max:190',
|
|
'message' => 'required|string|max:5000',
|
|
'privacy' => 'accepted',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function messages(): array
|
|
{
|
|
return [
|
|
'name.required' => 'Bitte geben Sie Ihren Namen an.',
|
|
'email.required' => 'Bitte geben Sie Ihre E-Mail-Adresse an.',
|
|
'email.email' => 'Bitte geben Sie eine gültige E-Mail-Adresse an.',
|
|
'message.required' => 'Bitte beschreiben Sie kurz Ihr Anliegen.',
|
|
'privacy.accepted' => 'Bitte akzeptieren Sie die Datenschutzerklärung.',
|
|
];
|
|
}
|
|
|
|
public function submit(): void
|
|
{
|
|
// Spam-Schutz: Bots befüllen die versteckten Felder. Wir tun so, als sei alles gut.
|
|
if ($this->company !== '' || $this->url !== '') {
|
|
$this->markeAlsGesendet('Danke. Ihre Anfrage wurde aufgenommen.');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->validate();
|
|
|
|
$key = 'audit-anfrage:'.request()->ip();
|
|
if (RateLimiter::tooManyAttempts($key, maxAttempts: 3)) {
|
|
$this->status = 'Zu viele Anfragen in kurzer Zeit. Bitte versuchen Sie es später erneut oder schreiben Sie an info@markemacht.de.';
|
|
|
|
return;
|
|
}
|
|
RateLimiter::hit($key, decaySeconds: 600);
|
|
|
|
Mail::to(config('marketing.anfrage_empfaenger'))->send(
|
|
new AuditAnfrage($this->name, $this->email, $this->message),
|
|
);
|
|
|
|
$this->markeAlsGesendet('Danke. Ihre Anfrage wurde versendet.');
|
|
}
|
|
|
|
private function markeAlsGesendet(string $meldung): void
|
|
{
|
|
$this->reset(['name', 'email', 'message', 'privacy', 'company', 'url']);
|
|
$this->status = $meldung;
|
|
$this->sent = true;
|
|
|
|
// markemacht.js lauscht auf dieses Event und schließt das Modal nach kurzer Zeit.
|
|
$this->dispatch('audit-sent');
|
|
}
|
|
};
|
|
?>
|
|
|
|
<div>
|
|
@if ($sent)
|
|
<p class="form-status body-text" aria-live="polite">{{ $status }}</p>
|
|
@else
|
|
<form class="modal-form stack-16" wire:submit="submit" novalidate>
|
|
{{-- Honeypot: außerhalb des sichtbaren Flusses, für Bots --}}
|
|
<div aria-hidden="true" tabindex="-1" style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;">
|
|
<label>Firma<input type="text" tabindex="-1" autocomplete="off" wire:model="company" /></label>
|
|
<label>URL<input type="text" tabindex="-1" autocomplete="off" wire:model="url" /></label>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label class="meta" for="audit-name">Name</label>
|
|
<input id="audit-name" type="text" autocomplete="name" wire:model="name" />
|
|
@error('name') <span class="form-error meta" role="alert">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label class="meta" for="audit-email">E-Mail</label>
|
|
<input id="audit-email" type="email" autocomplete="email" wire:model="email" />
|
|
@error('email') <span class="form-error meta" role="alert">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label class="meta" for="audit-message">Worum geht es?</label>
|
|
<textarea id="audit-message" rows="5" wire:model="message"></textarea>
|
|
@error('message') <span class="form-error meta" role="alert">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<label class="form-check">
|
|
<input type="checkbox" wire:model="privacy" />
|
|
<span class="form-switch" aria-hidden="true"></span>
|
|
<span>Ich akzeptiere die <a href="{{ route('datenschutz') }}">Datenschutzerklärung</a>.</span>
|
|
</label>
|
|
@error('privacy') <span class="form-error meta" role="alert">{{ $message }}</span> @enderror
|
|
|
|
<p class="form-status body-text" aria-live="polite">{{ $status }}</p>
|
|
|
|
<div>
|
|
<button class="btn btn-nav-cta" type="submit" wire:loading.attr="disabled" wire:target="submit">
|
|
<span wire:loading.remove wire:target="submit">Absenden</span>
|
|
<span wire:loading wire:target="submit">Senden …</span>
|
|
<svg class="icon" aria-hidden="true"><use href="/web/markemacht/icons.svg#arrow-forward"></use></svg>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
@endif
|
|
</div>
|