Startseite und Heroes - Hero-Slideshow (11 Motive inkl. Febrü) mit Infokarte, Laufzeit-Linie, größeren Klickflächen, Goldrahmen und mobiler Karte unter dem Bild; Zeile über der Überschrift, neue Vertrauenspunkte - Hero-Stil auf Marken, Über uns und Kontakt übertragen, Breadcrumb entfernt - Gründer-Leiste, Vision-Box auf Ocker, Showroom-Display in Box mit „Größer ansehen“/Vollbild - Akzentfarbe #a8741f, größere Serif-Überschriften in der Interior-Welt - Marken: Bildpause mit Zitat; Vereine aktualisiert und verlinkt Inhalte - Lektorat aller angezeigten Texte, /about-Korrekturen, Rechtstexte (DDG, TDDDG, Hosting Hetzner) - Ablauf Maßanfertigung überall einheitlich, Magazin-Beitrag neu, alte Beiträge offline, deutsches Datum/Lesezeit, FAQ Projektausstattung, Otterpohl - Referenzen-Seite per CMS-Schalter ausblendbar CMS und Technik - Tote Sektionen, Felder, Seiten und Komponenten entfernt und archiviert (dev/2026-08-28/archiv-*) - Entwurf /entwurf, Farb-Schalter und /welcome entfernt; Test-Routen nur noch lokal - Seitenbaum-Beschreibungen geprüft, fehlende CMS-Felder ergänzt, Markenliste aus „Kernmarken“ - Admin-Bildvorschauen repariert, verwendete Bilder in die Mediathek übernommen - Seeder für das Deployment siehe dev/2026-08-28/entwicklungsplan.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
205 lines
6.1 KiB
PHP
205 lines
6.1 KiB
PHP
<?php
|
||
|
||
namespace App\Livewire\Web\Components\Ui;
|
||
|
||
use Acme\ContactForm\ContactFormService;
|
||
use Acme\ContactForm\SpamDetector;
|
||
use App\Models\CmsBrand;
|
||
use Illuminate\Validation\Rule;
|
||
use Livewire\Component;
|
||
|
||
class ContactForm extends Component
|
||
{
|
||
public string $firstName = '';
|
||
|
||
public string $lastName = '';
|
||
|
||
public string $company = '';
|
||
|
||
public string $email = '';
|
||
|
||
public string $phone = '';
|
||
|
||
public string $subject = '';
|
||
|
||
public string $message = '';
|
||
|
||
public bool $privacy = false;
|
||
|
||
public bool $success = false;
|
||
|
||
/** @var string Hidden honeypot field */
|
||
public string $website = '';
|
||
|
||
public ?int $formLoadedAt = null;
|
||
|
||
/** @var array<string, mixed> */
|
||
public array $content = [];
|
||
|
||
/** Farbwelt der Seite (`interior` | `immobilien`). */
|
||
public string $world = 'interior';
|
||
|
||
public function mount(string $world = 'interior'): void
|
||
{
|
||
$this->world = $world === 'immobilien' ? 'immobilien' : 'interior';
|
||
$this->content = $this->loadContent();
|
||
$this->formLoadedAt = time();
|
||
}
|
||
|
||
/**
|
||
* Inhalte der Welt. Die Immobilien-Welt liegt als Overlay über der Interior-Sektion:
|
||
* `contact_form_immobilien` trägt nur, was abweicht (Einstieg, Betreffe) – Adresse,
|
||
* Beschriftungen und Social Media erbt sie von `contact_form`.
|
||
*
|
||
* @return array<string, mixed>
|
||
*/
|
||
private function loadContent(): array
|
||
{
|
||
$base = cms_theme_section('contact_form');
|
||
$base = is_array($base) ? $base : [];
|
||
|
||
if ($this->world !== 'immobilien') {
|
||
return $base;
|
||
}
|
||
|
||
$overlay = cms_theme_section('contact_form_immobilien');
|
||
|
||
if (! is_array($overlay)) {
|
||
return $base;
|
||
}
|
||
|
||
return array_replace_recursive($base, $overlay);
|
||
}
|
||
|
||
/**
|
||
* Betreffe in Gruppen: Interior (mit den gepflegten Kernmarken vorneweg), Immobilien, B2in.
|
||
* Die Welt der Seite bestimmt die Reihenfolge – ihre Gruppe steht oben.
|
||
*
|
||
* Ohne `subject_groups` (aeltere CMS-Staende) faellt das Formular auf die flache Liste zurueck.
|
||
*
|
||
* @return array<string, array{label: string, subjects: array<string, string>}>
|
||
*/
|
||
public function getSubjectGroupsProperty(): array
|
||
{
|
||
$groups = $this->content['form']['subject_groups'] ?? null;
|
||
|
||
if (! is_array($groups) || $groups === []) {
|
||
$flat = $this->content['form']['subjects'] ?? [];
|
||
unset($flat['']);
|
||
|
||
return $flat === [] ? [] : ['all' => ['label' => '', 'subjects' => $flat]];
|
||
}
|
||
|
||
$brands = [];
|
||
|
||
foreach (CmsBrand::forFrontend() as $slug => $brand) {
|
||
$brands[$slug] = (string) ($brand['name'] ?? $slug);
|
||
}
|
||
|
||
$out = [];
|
||
|
||
foreach ($groups as $key => $group) {
|
||
$subjects = is_array($group['subjects'] ?? null) ? $group['subjects'] : [];
|
||
|
||
if ($key === 'interior') {
|
||
$subjects = $brands + $subjects;
|
||
}
|
||
|
||
$out[$key] = [
|
||
'label' => (string) ($group['label'] ?? ''),
|
||
'subjects' => $subjects,
|
||
];
|
||
}
|
||
|
||
$first = $this->world === 'immobilien' ? 'immobilien' : 'interior';
|
||
|
||
if (isset($out[$first])) {
|
||
$out = [$first => $out[$first]] + $out;
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Alle waehlbaren Betreffe als flache Liste (Schluessel → Beschriftung).
|
||
*
|
||
* @return array<string, string>
|
||
*/
|
||
public function getSubjectsProperty(): array
|
||
{
|
||
$flat = [];
|
||
|
||
foreach ($this->getSubjectGroupsProperty() as $group) {
|
||
$flat += $group['subjects'];
|
||
}
|
||
|
||
return $flat;
|
||
}
|
||
|
||
public function getSubjectPlaceholderProperty(): string
|
||
{
|
||
return (string) ($this->content['form']['subjects'][''] ?? $this->content['form']['subject_placeholder'] ?? __('ui.contact_form.subject_placeholder'));
|
||
}
|
||
|
||
/** @return array<int, array<string, mixed>> */
|
||
public function getContactInfoProperty(): array
|
||
{
|
||
return $this->content['contact_info'] ?? [];
|
||
}
|
||
|
||
public function submit(ContactFormService $service): void
|
||
{
|
||
$this->validate([
|
||
'firstName' => ['required', 'string', 'max:255'],
|
||
'lastName' => ['required', 'string', 'max:255'],
|
||
'email' => ['required', 'email:rfc', 'max:255'],
|
||
'subject' => ['required', 'string', Rule::in(array_keys($this->subjects))],
|
||
'message' => ['required', 'string', 'max:2000'],
|
||
'company' => ['nullable', 'string', 'max:255'],
|
||
'phone' => ['nullable', 'string', 'max:255'],
|
||
'privacy' => ['accepted'],
|
||
'website' => ['nullable', 'string', 'max:0'],
|
||
]);
|
||
|
||
$spamDetector = SpamDetector::fromConfig();
|
||
$payload = [
|
||
'first_name' => $this->firstName,
|
||
'last_name' => $this->lastName,
|
||
'email' => $this->email,
|
||
'subject' => $this->subject,
|
||
'message' => $this->message,
|
||
'company' => $this->company,
|
||
'phone' => $this->phone,
|
||
'website' => $this->website,
|
||
'ip' => request()->ip(),
|
||
'user_agent' => request()->userAgent(),
|
||
'is_spam' => false,
|
||
];
|
||
|
||
$payload['is_spam'] = $spamDetector->detect($payload, $this->formLoadedAt);
|
||
|
||
$subjectLabel = $this->getSubjectsProperty()[$this->subject] ?? $this->subject;
|
||
$prefix = $this->world === 'immobilien' ? 'Kontaktanfrage B2in Immobilien' : 'Kontaktanfrage B2in';
|
||
$subjectLine = $subjectLabel
|
||
? $prefix.': '.$subjectLabel
|
||
: __('contact-form::contact-form.default_subject');
|
||
$service->handle($payload, $subjectLine, 'contact-form');
|
||
|
||
$this->success = true;
|
||
$this->firstName = '';
|
||
$this->lastName = '';
|
||
$this->company = '';
|
||
$this->email = '';
|
||
$this->phone = '';
|
||
$this->subject = '';
|
||
$this->message = '';
|
||
$this->privacy = false;
|
||
$this->website = '';
|
||
$this->formLoadedAt = time();
|
||
}
|
||
|
||
public function render(): \Illuminate\View\View
|
||
{
|
||
return view('livewire.web.components.ui.contact-form');
|
||
}
|
||
}
|