feat(marketing): Audit-Anfrageformular als Livewire-Komponente
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>
This commit is contained in:
parent
9c6b7ed4f3
commit
1f7c0aa430
7 changed files with 527 additions and 0 deletions
47
app/Mail/AuditAnfrage.php
Normal file
47
app/Mail/AuditAnfrage.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AuditAnfrage extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $anfrageName,
|
||||
public string $anfrageEmail,
|
||||
public string $anfrageMessage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* Antwort geht direkt an den Absender der Anfrage (Reply-To).
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Neue Anfrage über markemacht.de',
|
||||
replyTo: [new Address($this->anfrageEmail, $this->anfrageName)],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
view: 'mail.audit-anfrage',
|
||||
);
|
||||
}
|
||||
}
|
||||
18
config/marketing.php
Normal file
18
config/marketing.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Marketing-Konfiguration (markemacht.de)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Einstellungen rund um die öffentliche Landingpage. Aktuell der Empfänger
|
||||
| für Anfragen aus dem Audit-/Kontaktformular. Lokal landet alles in Mailpit
|
||||
| (siehe MAIL_*); der echte Empfänger wird per ENV auf dem Server gesetzt.
|
||||
|
|
||||
*/
|
||||
|
||||
'anfrage_empfaenger' => env('MARKETING_ANFRAGE_TO', 'info@markemacht.de'),
|
||||
|
||||
];
|
||||
164
dev/audit-formular-cookies/UMSETZUNGSPLAN.md
Normal file
164
dev/audit-formular-cookies/UMSETZUNGSPLAN.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# Umsetzungsplan — Audit-Formular (Laravel) + Cookie-/Datenschutz-Anpassung
|
||||
|
||||
**Kontext:** Die statische markemacht.de wurde in die Laravel-App integriert. Zwei offene
|
||||
Punkte: (1) das Anfrage-/Audit-Formular ist nach der Migration tot und muss als Laravel-Feature
|
||||
wieder funktionieren, (2) Laravel setzt jetzt System-Cookies — der Hinweis „Keine Cookies" und
|
||||
die Datenschutzerklärung müssen ehrlich werden.
|
||||
|
||||
> Dieses Dokument ist **lebend**: Es wird nach jedem Checkpoint aktualisiert. Wer die Arbeit
|
||||
> (auch an einem anderen Tag) wieder aufnimmt, liest zuerst **📍 Aktueller Stand** und macht
|
||||
> beim ersten offenen Checkpoint weiter. Jeder Checkpoint ist so geschnitten, dass der Code
|
||||
> danach in sich konsistent ist (Tests grün, nichts halb-fertig).
|
||||
|
||||
---
|
||||
|
||||
## 📍 Aktueller Stand _(immer zuerst lesen)_
|
||||
|
||||
- **Phase:** ✅ 1 + 2 abgeschlossen.
|
||||
- **Zuletzt erledigt:** **Phase 2 komplett & verifiziert** — mm-wirkung-Cookie entfernt (JS+Blade,
|
||||
aus Build bestätigt), Footer „DSGVO-konform · keine Weitergabe", Datenschutz neu, 61/61 Tests grün,
|
||||
Build neu (`markemacht-NNJhNDv3.js`).
|
||||
- **Als Nächstes:** Phase 3 (Klarheits-Check) — eigener Plan/PR, separat. ODER offene Commit-Grenze klären.
|
||||
- **Offen (Entscheidung User):** Commit-Grenze. Branch ist komplett WIP/uncommittet; die Phase-1/2-
|
||||
Dateien sind NICHT separat committet, um die laufende Integration nicht zu fragmentieren.
|
||||
- **Branch:** `feat/markemacht-laravel-migration`
|
||||
- **Wiederaufnahme:** Diesen Block lesen → ersten offenen `[ ]`-Checkpoint suchen → dort
|
||||
„Verifikation" am Ende ausführen, um den Ist-Zustand zu bestätigen, dann weiterarbeiten.
|
||||
|
||||
---
|
||||
|
||||
## Gesperrte Entscheidungen (nicht mehr offen)
|
||||
|
||||
- Audit-Formular wird **Livewire-SFC** `web.audit-form` (Projekt-Konvention: Single-File-Component
|
||||
mit ⚡-Präfix unter `resources/views/components/web/⚡audit-form.blade.php`, **nicht** class-based).
|
||||
Tag im Markup: `<livewire:web.audit-form />`. Modal-Chrome (öffnen/schließen/Fokus in
|
||||
`markemacht.js`) bleibt Vanilla-JS unverändert.
|
||||
- Mailversand über **Mailable** + `config/mail.php`. **Lokal: Mailpit** (Mutterschiff). Echte
|
||||
SMTP-Creds kommen später in die Server-`.env` — Code liest nur aus `config('mail')`.
|
||||
- Klarheits-Check ist **nicht** Teil dieses Plans (eigenes Feature/PR, Controller-basiert).
|
||||
- Cookie-Strategie: nur technisch notwendige Cookies (`*_session`, `XSRF-TOKEN`).
|
||||
`mm-wirkung`-Cookie wird **entfernt** → localStorage-only. **Kein Cookie-Banner** (kein Rechtsrat).
|
||||
- Footer-Text statt „Keine Cookies": **„DSGVO-konform · keine Weitergabe"**.
|
||||
|
||||
---
|
||||
|
||||
## Fortschritt (Überblick)
|
||||
|
||||
### Phase 1 — Audit-Formular funktionsfähig (Livewire)
|
||||
- [x] **CP 1.1** Mail-Fundament: `AuditAnfrage`-Mailable + Mail-View, Config/ENV geprüft
|
||||
- [x] **CP 1.2** Livewire-Komponente `AuditForm` (Validierung, Honeypot, Throttle, Versand)
|
||||
- [x] **CP 1.3** Modal-Markup auf `<livewire:web.audit-form />` umstellen; Livewire-Assets sicherstellen
|
||||
- [x] **CP 1.4** Tests (`Mail::fake`, Validierung, Honeypot, Throttle) + Pint — 58/58 grün
|
||||
- [x] **CP 1.5** Manuelle Verifikation in Mailpit (Commit: an User-Commit-Grenze gekoppelt)
|
||||
|
||||
### Phase 2 — Cookies & Datenschutz ehrlich
|
||||
- [x] **CP 2.1** `mm-wirkung`-Cookie entfernen (JS + Blade-Fallback) → localStorage-only
|
||||
- [x] **CP 2.2** Footer-Text „Keine Cookies" → „DSGVO-konform · keine Weitergabe"
|
||||
- [x] **CP 2.3** Datenschutz: Cookie-Absatz neu (technisch notwendige Cookies) + Aussage Datenweitergabe
|
||||
- [x] **CP 2.4** Tests (Footer-Text, Datenschutz-Inhalt) + Pint — 61/61 grün, Build neu
|
||||
|
||||
### Phase 3 — (separat, nur notiert)
|
||||
- Klarheits-Check: Blade + JS + `POST /klarheits-check/auswerten` (Anthropic-Proxy),
|
||||
inkl. KI-Datenschutz-Absatz. Eigener Plan/PR.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Audit-Formular (Detail)
|
||||
|
||||
**Altes Verhalten (Referenz):** `_markemacht.de/assets/php/send-anfrage.php` — POST, Felder
|
||||
`name`, `email`, `message`, `privacy` (Pflicht-Checkbox), Honeypot `company`/`url`, Versand per
|
||||
PHPMailer/SMTP, JSON-Antwort `{ok, message}`. Empfänger/Betreff aus `config.php`.
|
||||
|
||||
### CP 1.1 — Mail-Fundament
|
||||
- [ ] `php artisan make:mail AuditAnfrage --markdown=mail.audit-anfrage` (oder plain Blade-View)
|
||||
- [ ] Mailable: Felder `name`, `email`, `message`; `Reply-To` = Absender; `Subject`
|
||||
„Neue Anfrage über markemacht.de"; Empfänger aus `config` (z. B. `config('mail.from')`
|
||||
oder neue `config('marketing.anfrage_empfaenger')`).
|
||||
- [ ] Empfänger-Adresse: entscheiden, wo sie herkommt (ENV `MARKETING_ANFRAGE_TO`, Default
|
||||
`info@markemacht.de`). In `config/` ablegen, nicht hardcoden.
|
||||
- **Verifikation:** `php artisan test` läuft weiter grün; Mailable-Klasse existiert.
|
||||
- **Wiederaufnahme-Notiz (erledigt 2026-06-18):** Mailable `App\Mail\AuditAnfrage`
|
||||
(ctor: `anfrageName`, `anfrageEmail`, `anfrageMessage`; `replyTo` = Absender; Subject „Neue
|
||||
Anfrage über markemacht.de"). View `resources/views/mail/audit-anfrage.blade.php`. Empfänger:
|
||||
`config('marketing.anfrage_empfaenger')` ← `MARKETING_ANFRAGE_TO`, Default `info@markemacht.de`
|
||||
(neue Datei `config/marketing.php`). Render + Inhalt verifiziert.
|
||||
|
||||
### CP 1.2 — Livewire-Komponente
|
||||
- [ ] `php artisan make:livewire Web/AuditForm` (Livewire-4-Konvention im Projekt prüfen)
|
||||
- [ ] Properties + Rules: `name` required, `email` required/email, `message` required,
|
||||
`privacy` accepted; Honeypot `company`/`url` (müssen leer sein).
|
||||
- [ ] `submit()`: Honeypot-Check (still „erfolgreich" bei Befüllung), Validierung,
|
||||
`RateLimiter` (z. B. 3 Anfragen / 10 Min pro IP), `Mail::to(...)->send(new AuditAnfrage(...))`,
|
||||
Erfolg → `$this->dispatch('audit-sent')` (das hört `markemacht.js:139` bereits),
|
||||
Felder leeren, Status-Property setzen.
|
||||
- [ ] View `resources/views/livewire/web/audit-form.blade.php`: 1:1 die bestehenden Felder/Klassen
|
||||
aus `footer.blade.php` übernehmen (Designsystem unverändert), `wire:model`, `wire:submit`,
|
||||
`@error`, `wire:loading`, `aria-live`-Status.
|
||||
- **Verifikation:** Komponente rendert isoliert (`Livewire::test`).
|
||||
- **Wiederaufnahme-Notiz:** _(Throttle-Limits, Event-Name, Validierungsregeln dokumentieren)_
|
||||
|
||||
### CP 1.3 — Einbau ins Modal
|
||||
- [ ] In `resources/views/web/partials/footer.blade.php` den `<form data-audit-form …>`-Block
|
||||
(Zeilen ~45–77) durch `<livewire:web.audit-form />` ersetzen. Hülle `[data-audit-form-panel]`
|
||||
behalten oder in die Komponente ziehen — Modal-Chrome bleibt.
|
||||
- [ ] Sicherstellen, dass Livewire-Assets auf Marketing-Seiten geladen werden (Default-Auto-Inject
|
||||
prüfen; ggf. `@livewireScripts`/`@livewireStyles` ins Marketing-Layout). **Achtung:** vorher
|
||||
lud die Marketing-Seite KEIN Livewire — hier genau testen (Konsole auf Fehler prüfen).
|
||||
- **Verifikation:** Seite `/audit` und Footer-Modal öffnen, Formular sichtbar, keine JS-Fehler.
|
||||
- **Wiederaufnahme-Notiz:** _(ob Layout angepasst werden musste)_
|
||||
|
||||
### CP 1.4 — Tests
|
||||
- [ ] Feature-/Livewire-Test in `tests/Feature/Web/`: erfolgreicher Versand (`Mail::fake`,
|
||||
`assertSent`), Validierungsfehler, Honeypot (kein Versand, aber „ok"), Throttle.
|
||||
- [ ] `php artisan test --compact --filter=Audit`
|
||||
- [ ] `vendor/bin/pint --dirty`
|
||||
- **Verifikation:** alle neuen Tests grün, Gesamt-Suite grün.
|
||||
|
||||
### CP 1.5 — Manuell + Commit
|
||||
- [ ] Lokal echtes Absenden → Mail erscheint in **Mailpit**.
|
||||
- [ ] Commit: `feat(marketing): Audit-Anfrageformular als Livewire-Komponente`.
|
||||
- **Verifikation:** `git log` zeigt Commit; Arbeitsbaum sauber bzgl. Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Cookies & Datenschutz (Detail)
|
||||
|
||||
### CP 2.1 — mm-wirkung-Cookie entfernen
|
||||
- [ ] `resources/js/web/markemacht.js:28` (`document.cookie = …mm-wirkung…`) entfernen.
|
||||
- [ ] `resources/views/web/layouts/marketing.blade.php:53` Cookie-Fallback im Inline-Script
|
||||
entfernen (localStorage genügt, läuft vor erstem Paint).
|
||||
- [ ] `npm run build` nötig (JS/Blade-Asset) — User bitten oder ausführen.
|
||||
- **Verifikation:** Theme-Umschalter funktioniert weiter; im DevTools-Application-Tab kein
|
||||
`mm-wirkung`-Cookie mehr, nur localStorage.
|
||||
|
||||
### CP 2.2 — Footer-Text
|
||||
- [ ] `resources/views/web/partials/footer.blade.php:6` „Keine Cookies" →
|
||||
„DSGVO-konform · keine Weitergabe".
|
||||
- **Verifikation:** Footer zeigt neuen Text.
|
||||
|
||||
### CP 2.3 — Datenschutzerklärung
|
||||
- [ ] `resources/views/web/datenschutz.blade.php:225-226`: „verwendet keine Cookies" ersetzen durch
|
||||
Absatz **„Technisch notwendige Cookies"**: `markemacht_session` + `XSRF-TOKEN`, Zweck
|
||||
(Bereitstellung/Absicherung der Formulare), Art. 6 Abs. 1 lit. f DSGVO / § 25 Abs. 2 TTDSG,
|
||||
keine Einwilligung nötig, kein Tracking.
|
||||
- [ ] localStorage-Absatz (228–233) bleibt korrekt → behalten.
|
||||
- [ ] Aussage zur **Datenweitergabe** ergänzen/prüfen (Umami self-hosted, keine Dritten) — passt
|
||||
zur Footer-Aussage und zum bestehenden Umami-Absatz (285+).
|
||||
- **Verifikation:** Datenschutzseite konsistent, keine Aussage „keine Cookies" mehr.
|
||||
|
||||
### CP 2.4 — Tests + Commit
|
||||
- [ ] Test in `tests/Feature/Web/`: Footer enthält neuen Text / nicht mehr „Keine Cookies";
|
||||
Datenschutz enthält „Technisch notwendige Cookies".
|
||||
- [ ] `php artisan test --compact` + `vendor/bin/pint --dirty`
|
||||
- [ ] Commit: `feat(marketing): Cookie-Hinweis & Datenschutz an Laravel-Integration angepasst`.
|
||||
- **Verifikation:** Suite grün, Commit gesetzt.
|
||||
|
||||
---
|
||||
|
||||
## Changelog (Arbeitsprotokoll)
|
||||
|
||||
| Datum | Checkpoint | Ergebnis / Notiz |
|
||||
|---|---|---|
|
||||
| 2026-06-18 | Setup | Plan angelegt, Entscheidungen gesperrt, Footer-Text = „DSGVO-konform · keine Weitergabe". |
|
||||
| 2026-06-18 | Phase 1 | Mailable `AuditAnfrage` + Config `marketing.anfrage_empfaenger`, Livewire-SFC `web.audit-form`, Modal-Einbau, Tests, Mailpit-E2E. |
|
||||
| 2026-06-18 | Phase 2 | mm-wirkung-Cookie raus (localStorage-only), Footer-Hinweis, Datenschutz „Technisch notwendige Cookies", Tests, Build. |
|
||||
140
resources/views/components/web/⚡audit-form.blade.php
Normal file
140
resources/views/components/web/⚡audit-form.blade.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?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>
|
||||
18
resources/views/mail/audit-anfrage.blade.php
Normal file
18
resources/views/mail/audit-anfrage.blade.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Neue Anfrage über markemacht.de</title>
|
||||
</head>
|
||||
<body style="font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; color: #1a1a1a; line-height: 1.5;">
|
||||
<h2 style="margin-bottom: 1rem;">Neue Anfrage über markemacht.de</h2>
|
||||
|
||||
<p>
|
||||
<strong>Name:</strong> {{ $anfrageName }}<br>
|
||||
<strong>E-Mail:</strong> {{ $anfrageEmail }}
|
||||
</p>
|
||||
|
||||
<p><strong>Nachricht:</strong></p>
|
||||
<p style="white-space: pre-wrap;">{{ $anfrageMessage }}</p>
|
||||
</body>
|
||||
</html>
|
||||
48
resources/views/web/partials/footer.blade.php
Normal file
48
resources/views/web/partials/footer.blade.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
@php $sprite = '/web/markemacht/icons.svg'; @endphp
|
||||
<footer class="site-footer">
|
||||
<div class="footer-inner">
|
||||
<div class="footer-meta">
|
||||
<p class="footer-copy">© <strong>Marke macht<span class="accent">.</span></strong> · Denkraum für Unternehmer</p>
|
||||
<p class="footer-note">Keine Cookies</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<a class="footer-link" href="{{ route('impressum') }}">Impressum</a>
|
||||
<a class="footer-link" href="{{ route('datenschutz') }}">Datenschutz</a>
|
||||
<button class="footer-link" type="button" data-audit-modal-open>Anfrage</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="modal-backdrop" data-audit-modal aria-hidden="true">
|
||||
<div class="modal-dialog" role="dialog" aria-modal="true" aria-labelledby="audit-modal-title" aria-describedby="audit-modal-desc">
|
||||
<button class="modal-close" type="button" data-audit-modal-close aria-label="Anfragefenster schließen">
|
||||
<svg class="icon" aria-hidden="true"><use href="{{ $sprite }}#close"></use></svg>
|
||||
</button>
|
||||
|
||||
<div class="stack-32">
|
||||
<div class="stack-16">
|
||||
<span class="meta accent">Anfrage</span>
|
||||
<h2 class="section-headline" id="audit-modal-title" style="max-width: 16ch;">Kurzer Erstkontakt.</h2>
|
||||
<p class="body-text max-prose" id="audit-modal-desc">
|
||||
Wählen Sie den direkten E-Mail-Weg oder senden Sie eine knappe Anfrage über das Formular.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card stack-16">
|
||||
<span class="meta">Direkt per E-Mail</span>
|
||||
<p class="body-text">
|
||||
Öffnet Ihr E-Mail-Programm mit vorausgefülltem Betreff.
|
||||
</p>
|
||||
<div>
|
||||
<a class="btn btn-nav-cta" href="mailto:info@markemacht.de?subject=Anfrage%20Marken-Klarheits-Audit">
|
||||
E-Mail öffnen
|
||||
<svg class="icon" aria-hidden="true"><use href="{{ $sprite }}#arrow-forward"></use></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Anfrageformular: Livewire-Komponente (Validierung + Versand serverseitig). --}}
|
||||
<livewire:web.audit-form />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
92
tests/Feature/Web/AuditFormTest.php
Normal file
92
tests/Feature/Web/AuditFormTest.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
use App\Mail\AuditAnfrage;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Livewire\Livewire;
|
||||
|
||||
use function Pest\Laravel\get;
|
||||
|
||||
test('das anfrageformular ist im audit-modal eingebunden', function () {
|
||||
get(route('audit'))
|
||||
->assertOk()
|
||||
->assertSee('wire:submit', false)
|
||||
->assertSee('livewire', false);
|
||||
});
|
||||
|
||||
test('leeres formular zeigt validierungsfehler und versendet nichts', function () {
|
||||
Mail::fake();
|
||||
|
||||
Livewire::test('web.audit-form')
|
||||
->call('submit')
|
||||
->assertHasErrors(['name', 'email', 'message', 'privacy']);
|
||||
|
||||
Mail::assertNothingSent();
|
||||
});
|
||||
|
||||
test('ungültige e-mail wird abgewiesen', function () {
|
||||
Livewire::test('web.audit-form')
|
||||
->set('name', 'Max Muster')
|
||||
->set('email', 'keine-email')
|
||||
->set('message', 'Text')
|
||||
->set('privacy', true)
|
||||
->call('submit')
|
||||
->assertHasErrors(['email']);
|
||||
});
|
||||
|
||||
test('gültige anfrage wird versendet und löst das schließen-event aus', function () {
|
||||
Mail::fake();
|
||||
|
||||
Livewire::test('web.audit-form')
|
||||
->set('name', 'Max Muster')
|
||||
->set('email', 'max@example.com')
|
||||
->set('message', 'Wir brauchen Klarheit.')
|
||||
->set('privacy', true)
|
||||
->call('submit')
|
||||
->assertHasNoErrors()
|
||||
->assertSet('sent', true)
|
||||
->assertDispatched('audit-sent');
|
||||
|
||||
Mail::assertSent(AuditAnfrage::class, function (AuditAnfrage $mail) {
|
||||
return $mail->hasTo(config('marketing.anfrage_empfaenger'))
|
||||
&& $mail->hasReplyTo('max@example.com');
|
||||
});
|
||||
});
|
||||
|
||||
test('honeypot-feld unterdrückt den versand, meldet aber erfolg', function () {
|
||||
Mail::fake();
|
||||
|
||||
Livewire::test('web.audit-form')
|
||||
->set('name', 'Bot')
|
||||
->set('email', 'bot@example.com')
|
||||
->set('message', 'spam')
|
||||
->set('privacy', true)
|
||||
->set('company', 'ausgefuellt')
|
||||
->call('submit')
|
||||
->assertHasNoErrors()
|
||||
->assertSet('sent', true);
|
||||
|
||||
Mail::assertNothingSent();
|
||||
});
|
||||
|
||||
test('zu viele anfragen werden gedrosselt', function () {
|
||||
Mail::fake();
|
||||
RateLimiter::clear('audit-anfrage:127.0.0.1');
|
||||
|
||||
$sende = fn () => Livewire::test('web.audit-form')
|
||||
->set('name', 'Max Muster')
|
||||
->set('email', 'max@example.com')
|
||||
->set('message', 'Anfrage')
|
||||
->set('privacy', true)
|
||||
->call('submit');
|
||||
|
||||
// Drei erlaubte Versuche …
|
||||
$sende();
|
||||
$sende();
|
||||
$sende();
|
||||
|
||||
// … der vierte wird gedrosselt: kein vierter Versand.
|
||||
$sende()->assertSet('sent', false);
|
||||
|
||||
Mail::assertSent(AuditAnfrage::class, 3);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue