b2in/app/Livewire/Web/Components/UI/ContactForm.php
Kevin Adametz 7953a8c9a1 Interior-Farbwelt, Welten-Trennung, Tischlerei und Herkunfts-Sektion
Arbeitsplan 14.09.2026, Punkte 1.1 bis 1.4 (Nachträge 6 bis 10 im Entwicklungsplan):

- Farbwelt Interior (Tanne, Creme, Sand, Ocker, Clay) als Standard, Immobilien-Welt
  behält das bisherige Blau über body.world-immobilien; Logo je Welt, Favicons neu,
  Welten-Wechsler in Kopf- und Fußzeile.
- Saubere Trennung der Welten: eigene Navigation, Ankündigungsleiste, Magazinliste,
  Über-uns-Seite, FAQ, Kontaktseite und Abschluss-CTA für Immobilien; Beiträge tragen
  eine Welt (Migration + Admin-Feld). Abschluss-CTA Ton in Ton statt Grün-zu-Ocker.
- Kontakt: Betreffe in Gruppen Interior / Immobilien / B2in, Marken darin.
  Terminanfrage-Buttons öffnen das Widget mit vorgewähltem Anliegen; Select-Felder
  einheitlich gestaltet.
- Tischlerei als vierte Kernmarke (Platzhalter), Kachelraster 2×2.
- Herkunfts-Sektion „Aus der Region gefertigt“ mit schematischer Standortkarte und
  Ablauf CABINET (Beratung Bielefeld, Fertigung Köln, Aufbau mit Tischlerei).

Deployment: migrate, WorldSplitCmsSeeder, CmsBrandSeeder, OriginSectionCmsSeeder
(Abschnitt 8 im Entwicklungsplan).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:46:41 +00:00

211 lines
6.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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'] ?? [];
}
/** @return array<int, array<string, mixed>> */
public function getSocialMediaProperty(): array
{
return $this->content['social_media']['platforms'] ?? [];
}
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');
}
}