From 5dad8b9324a8400a1a3559f91ea2e82a64911715 Mon Sep 17 00:00:00 2001 From: Marco Wende Date: Sat, 20 Jun 2026 22:04:54 +0200 Subject: [PATCH] Add email templates, page layouts, and shop themes (template system) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - E-Mail-Templates: DB-gespeicherte HTML-Templates mit {{placeholder}}-System, Admin-UI zum Bearbeiten von Betreff und Body, alle 4 Mailables aktualisiert - Seiten-Layouts: 4 Layouts für CMS-Seiten (Standard, Sidebar, Volle Breite, Landing), Layout-Auswahl im Admin-Formular - Shop-Themes: 5 Farbthemen via CSS-Variablen, Picker in Einstellungen - Deployment: EmailTemplateSeeder wird automatisch beim Deploy ausgeführt Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/deploy.yml | 1 + .../Admin/EmailTemplateController.php | 36 +++++ app/Http/Controllers/Admin/PageController.php | 2 + .../Controllers/Admin/SettingsController.php | 1 + app/Mail/OrderConfirmationMail.php | 68 ++++++++- app/Mail/OrderShippedMail.php | 29 +++- app/Mail/PaymentReceivedMail.php | 25 +++- app/Mail/WiderrufsformularMail.php | 31 +++- app/Models/EmailTemplate.php | 34 +++++ app/Models/Page.php | 2 +- config/themes.php | 39 +++++ ...01_000017_create_email_templates_table.php | 25 ++++ .../2024_01_01_000018_add_layout_to_pages.php | 21 +++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/EmailTemplateSeeder.php | 133 ++++++++++++++++++ .../admin/email-templates/edit.blade.php | 48 +++++++ .../admin/email-templates/index.blade.php | 43 ++++++ resources/views/admin/pages/form.blade.php | 8 ++ resources/views/layouts/admin.blade.php | 1 + resources/views/layouts/shop.blade.php | 111 +++++++++------ resources/views/shop/page.blade.php | 52 ++++++- .../views/shop/partials/_sidebar.blade.php | 39 +++++ routes/web.php | 5 + 23 files changed, 700 insertions(+), 55 deletions(-) create mode 100644 app/Http/Controllers/Admin/EmailTemplateController.php create mode 100644 app/Models/EmailTemplate.php create mode 100644 config/themes.php create mode 100644 database/migrations/2024_01_01_000017_create_email_templates_table.php create mode 100644 database/migrations/2024_01_01_000018_add_layout_to_pages.php create mode 100644 database/seeders/EmailTemplateSeeder.php create mode 100644 resources/views/admin/email-templates/edit.blade.php create mode 100644 resources/views/admin/email-templates/index.blade.php create mode 100644 resources/views/shop/partials/_sidebar.blade.php diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 320499c..5e27a63 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -45,6 +45,7 @@ jobs: /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan route:clear /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan view:clear /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan migrate --force + /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan db:seed --class=EmailTemplateSeeder --force /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan config:cache /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan route:cache /opt/plesk/php/8.4/bin/php -d open_basedir="" artisan view:cache diff --git a/app/Http/Controllers/Admin/EmailTemplateController.php b/app/Http/Controllers/Admin/EmailTemplateController.php new file mode 100644 index 0000000..6a40958 --- /dev/null +++ b/app/Http/Controllers/Admin/EmailTemplateController.php @@ -0,0 +1,36 @@ +get(); + return view('admin.email-templates.index', compact('templates')); + } + + public function edit(EmailTemplate $emailTemplate) + { + return view('admin.email-templates.edit', ['template' => $emailTemplate]); + } + + public function update(Request $request, EmailTemplate $emailTemplate) + { + $request->validate([ + 'subject' => 'required|string|max:255', + 'body' => 'required|string', + ]); + + $emailTemplate->update([ + 'subject' => $request->input('subject'), + 'body' => $request->input('body'), + ]); + + return back()->with('success', 'E-Mail-Template gespeichert.'); + } +} diff --git a/app/Http/Controllers/Admin/PageController.php b/app/Http/Controllers/Admin/PageController.php index fdc92f3..ede5394 100644 --- a/app/Http/Controllers/Admin/PageController.php +++ b/app/Http/Controllers/Admin/PageController.php @@ -59,11 +59,13 @@ class PageController extends Controller 'show_in_footer' => 'boolean', 'show_in_nav' => 'boolean', 'nav_sort' => 'integer|min:0', + 'layout' => 'nullable|in:default,sidebar,full,landing', ]); $data['slug'] = $data['slug'] ? Str::slug($data['slug']) : Str::slug($data['title']); $data['published'] = $request->boolean('published'); $data['show_in_footer'] = $request->boolean('show_in_footer'); $data['show_in_nav'] = $request->boolean('show_in_nav'); + $data['layout'] = $request->input('layout', 'default'); return $data; } } diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 6bb8e1f..debcdca 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -13,6 +13,7 @@ class SettingsController extends Controller 'bank_name','bank_iban','bank_bic','invoice_payment_days', 'smtp_host','smtp_port','smtp_user','smtp_password','smtp_from_name','smtp_from_email', 'cookie_consent','google_analytics','require_customer_approval', + 'theme_name', ]; public function index() diff --git a/app/Mail/OrderConfirmationMail.php b/app/Mail/OrderConfirmationMail.php index 22229ae..3a4625b 100644 --- a/app/Mail/OrderConfirmationMail.php +++ b/app/Mail/OrderConfirmationMail.php @@ -2,6 +2,7 @@ namespace App\Mail; +use App\Models\EmailTemplate; use App\Models\Order; use App\Models\Setting; use Illuminate\Bus\Queueable; @@ -16,8 +17,71 @@ class OrderConfirmationMail extends Mailable public function build() { + $order = $this->order; + $template = EmailTemplate::getByKey('order_confirmation'); + + if (!$template) { + return $this + ->subject('Bestellbestätigung ' . $order->order_number . ' – ' . Setting::get('shop_name')) + ->view('emails.order_confirmation'); + } + + $itemRows = ''; + foreach ($order->items as $item) { + $variant = $item->variant_label ? ' (' . e($item->variant_label) . ')' : ''; + $itemRows .= '' + . '' . e($item->product_name) . $variant . '' + . '' . $item->quantity . '' + . '' . number_format($item->unit_price, 2, ',', '.') . ' €' + . '' . number_format($item->line_total, 2, ',', '.') . ' €' + . ''; + } + $itemsTable = '' + . '' + . '' + . '' + . '' + . '' + . '' . $itemRows . '' + . '' + . '' + . '' + . '' + . '
ArtikelMengeEinzelpreisGesamt
Zwischensumme' . number_format($order->subtotal, 2, ',', '.') . ' €
Versand' . number_format($order->shipping_cost, 2, ',', '.') . ' €
Gesamt (inkl. MwSt.)' . number_format($order->total, 2, ',', '.') . ' €
'; + + if ($order->payment_method === 'prepayment') { + $paymentInfo = '
' + . 'Bitte überweisen Sie den Betrag auf folgendes Konto:
' + . 'Empfänger: ' . e(Setting::get('shop_owner')) . '
' + . 'Bank: ' . e(Setting::get('bank_name')) . '
' + . 'IBAN: ' . e(Setting::get('bank_iban')) . '
' + . 'BIC: ' . e(Setting::get('bank_bic')) . '
' + . 'Verwendungszweck: ' . e($order->order_number) . '
' + . 'Betrag: ' . number_format($order->total, 2, ',', '.') . ' €' + . '
'; + } else { + $days = Setting::get('invoice_payment_days', 14); + $paymentInfo = '
' + . 'Sie erhalten Ihre Rechnung separat per E-Mail.
' + . 'Zahlungsziel: ' . $days . ' Tage nach Rechnungserhalt.' + . '
'; + } + + $customerName = $order->billingAddress()?->full_name ?? $order->guest_email; + + $data = [ + 'customer_name' => $customerName, + 'order_number' => $order->order_number, + 'order_total' => number_format($order->total, 2, ',', '.'), + 'payment_info' => $paymentInfo, + 'order_items_table' => $itemsTable, + 'shop_name' => Setting::get('shop_name'), + 'shop_email' => Setting::get('shop_email'), + 'shop_address' => Setting::get('shop_address'), + ]; + return $this - ->subject('Bestellbestätigung ' . $this->order->order_number . ' – ' . Setting::get('shop_name')) - ->view('emails.order_confirmation'); + ->subject($template->renderSubject($data)) + ->html($template->renderBody($data)); } } diff --git a/app/Mail/OrderShippedMail.php b/app/Mail/OrderShippedMail.php index d0f6a30..2e5b08d 100644 --- a/app/Mail/OrderShippedMail.php +++ b/app/Mail/OrderShippedMail.php @@ -2,6 +2,7 @@ namespace App\Mail; +use App\Models\EmailTemplate; use App\Models\Order; use App\Models\Setting; use Illuminate\Bus\Queueable; @@ -16,8 +17,32 @@ class OrderShippedMail extends Mailable public function build() { + $order = $this->order; + $template = EmailTemplate::getByKey('order_shipped'); + + if (!$template) { + return $this + ->subject('Ihre Bestellung wurde versendet – ' . $order->order_number) + ->view('emails.order_shipped'); + } + + $trackingInfo = $order->tracking_code + ? '
' + . 'Sendungsverfolgung:
' + . 'Tracking-Nummer: ' . e($order->tracking_code) . '' + . '
' + : ''; + + $data = [ + 'order_number' => $order->order_number, + 'tracking_code' => $order->tracking_code ?? '', + 'tracking_info' => $trackingInfo, + 'shop_name' => Setting::get('shop_name'), + 'shop_email' => Setting::get('shop_email'), + ]; + return $this - ->subject('Ihre Bestellung wurde versendet – ' . $this->order->order_number) - ->view('emails.order_shipped'); + ->subject($template->renderSubject($data)) + ->html($template->renderBody($data)); } } diff --git a/app/Mail/PaymentReceivedMail.php b/app/Mail/PaymentReceivedMail.php index 41404b4..65fc896 100644 --- a/app/Mail/PaymentReceivedMail.php +++ b/app/Mail/PaymentReceivedMail.php @@ -2,6 +2,7 @@ namespace App\Mail; +use App\Models\EmailTemplate; use App\Models\Order; use App\Models\Setting; use Illuminate\Bus\Queueable; @@ -20,9 +21,29 @@ class PaymentReceivedMail extends Mailable public function build() { + $order = $this->order; + $template = EmailTemplate::getByKey('payment_received'); + + if (!$template) { + return $this + ->subject('Ihre Rechnung – ' . $order->order_number . ' – ' . Setting::get('shop_name')) + ->view('emails.payment_received') + ->attachData($this->pdfContent, $this->pdfFilename, ['mime' => 'application/pdf']); + } + + $customerName = $order->billingAddress()?->full_name ?? $order->guest_email; + + $data = [ + 'customer_name' => $customerName, + 'order_number' => $order->order_number, + 'order_total' => number_format($order->total, 2, ',', '.'), + 'shop_name' => Setting::get('shop_name'), + 'shop_email' => Setting::get('shop_email'), + ]; + return $this - ->subject('Ihre Rechnung – ' . $this->order->order_number . ' – ' . Setting::get('shop_name')) - ->view('emails.payment_received') + ->subject($template->renderSubject($data)) + ->html($template->renderBody($data)) ->attachData($this->pdfContent, $this->pdfFilename, ['mime' => 'application/pdf']); } } diff --git a/app/Mail/WiderrufsformularMail.php b/app/Mail/WiderrufsformularMail.php index a867fb9..e01c8a6 100644 --- a/app/Mail/WiderrufsformularMail.php +++ b/app/Mail/WiderrufsformularMail.php @@ -2,6 +2,7 @@ namespace App\Mail; +use App\Models\EmailTemplate; use App\Models\Setting; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; @@ -18,10 +19,32 @@ class WiderrufsformularMail extends Mailable public function build() { - $subject = $this->isConfirmation - ? 'Bestätigung Ihres Widerrufs – ' . Setting::get('shop_name') - : 'Neues Widerrufsformular eingegangen'; + if (!$this->isConfirmation) { + $subject = 'Neues Widerrufsformular eingegangen'; + return $this->subject($subject)->view('emails.widerruf'); + } - return $this->subject($subject)->view('emails.widerruf'); + $template = EmailTemplate::getByKey('widerruf_confirmation'); + + if (!$template) { + return $this + ->subject('Bestätigung Ihres Widerrufs – ' . Setting::get('shop_name')) + ->view('emails.widerruf'); + } + + $vars = [ + 'customer_name' => $this->data['name'], + 'widerruf_name' => $this->data['name'], + 'widerruf_email' => $this->data['email'], + 'widerruf_order_number' => $this->data['order_number'] ?? '—', + 'widerruf_product' => $this->data['product'], + 'widerruf_date' => $this->data['date'], + 'widerruf_reason' => $this->data['reason'] ?? '—', + 'shop_name' => Setting::get('shop_name'), + ]; + + return $this + ->subject($template->renderSubject($vars)) + ->html($template->renderBody($vars)); } } diff --git a/app/Models/EmailTemplate.php b/app/Models/EmailTemplate.php new file mode 100644 index 0000000..19360ae --- /dev/null +++ b/app/Models/EmailTemplate.php @@ -0,0 +1,34 @@ + 'array']; + + public static function getByKey(string $key): ?self + { + return static::where('key', $key)->first(); + } + + public function renderBody(array $data): string + { + $body = $this->body; + foreach ($data as $k => $v) { + $body = str_replace('{{' . $k . '}}', (string) $v, $body); + } + return $body; + } + + public function renderSubject(array $data): string + { + $subject = $this->subject; + foreach ($data as $k => $v) { + $subject = str_replace('{{' . $k . '}}', (string) $v, $subject); + } + return $subject; + } +} diff --git a/app/Models/Page.php b/app/Models/Page.php index b0fbc3a..59cf362 100644 --- a/app/Models/Page.php +++ b/app/Models/Page.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; class Page extends Model { - protected $fillable = ['slug','title','content','type','image','meta_title','meta_description','published','published_at','show_in_footer','show_in_nav','nav_sort']; + protected $fillable = ['slug','title','content','type','image','meta_title','meta_description','published','published_at','show_in_footer','show_in_nav','nav_sort','layout']; protected $casts = ['published' => 'boolean', 'published_at' => 'datetime', 'show_in_footer' => 'boolean', 'show_in_nav' => 'boolean']; } diff --git a/config/themes.php b/config/themes.php new file mode 100644 index 0000000..a0e2a4a --- /dev/null +++ b/config/themes.php @@ -0,0 +1,39 @@ + [ + 'name' => 'Blau (Standard)', + 'primary' => '#1d4ed8', + 'dark' => '#1e40af', + 'light' => '#eff6ff', + 'text' => '#1e40af', + ], + 'green' => [ + 'name' => 'Grün', + 'primary' => '#16a34a', + 'dark' => '#15803d', + 'light' => '#f0fdf4', + 'text' => '#15803d', + ], + 'red' => [ + 'name' => 'Rot', + 'primary' => '#dc2626', + 'dark' => '#b91c1c', + 'light' => '#fef2f2', + 'text' => '#b91c1c', + ], + 'purple' => [ + 'name' => 'Lila', + 'primary' => '#7c3aed', + 'dark' => '#6d28d9', + 'light' => '#f5f3ff', + 'text' => '#6d28d9', + ], + 'orange' => [ + 'name' => 'Orange', + 'primary' => '#ea580c', + 'dark' => '#c2410c', + 'light' => '#fff7ed', + 'text' => '#c2410c', + ], +]; diff --git a/database/migrations/2024_01_01_000017_create_email_templates_table.php b/database/migrations/2024_01_01_000017_create_email_templates_table.php new file mode 100644 index 0000000..18cf064 --- /dev/null +++ b/database/migrations/2024_01_01_000017_create_email_templates_table.php @@ -0,0 +1,25 @@ +id(); + $table->string('key')->unique(); + $table->string('label'); + $table->string('subject'); + $table->longText('body'); + $table->json('variables')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('email_templates'); + } +}; diff --git a/database/migrations/2024_01_01_000018_add_layout_to_pages.php b/database/migrations/2024_01_01_000018_add_layout_to_pages.php new file mode 100644 index 0000000..cb48c6b --- /dev/null +++ b/database/migrations/2024_01_01_000018_add_layout_to_pages.php @@ -0,0 +1,21 @@ +string('layout')->default('default')->after('show_in_footer'); + }); + } + + public function down(): void + { + Schema::table('pages', function (Blueprint $table) { + $table->dropColumn('layout'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index a97af88..fc30fbd 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -11,6 +11,7 @@ class DatabaseSeeder extends Seeder $this->call([ SettingsSeeder::class, AdminUserSeeder::class, + EmailTemplateSeeder::class, ]); } } diff --git a/database/seeders/EmailTemplateSeeder.php b/database/seeders/EmailTemplateSeeder.php new file mode 100644 index 0000000..aa6132d --- /dev/null +++ b/database/seeders/EmailTemplateSeeder.php @@ -0,0 +1,133 @@ + 'order_confirmation', + 'label' => 'Bestellbestätigung', + 'subject' => 'Bestellbestätigung {{order_number}} – {{shop_name}}', + 'variables' => ['customer_name','order_number','order_total','payment_info','order_items_table','shop_name','shop_email','shop_address','shop_owner','bank_name','bank_iban','bank_bic','invoice_payment_days'], + 'body' => <<<'HTML' + + + + +

Vielen Dank für Ihre Bestellung!

+

Guten Tag {{customer_name}},

+

wir haben Ihre Bestellung {{order_number}} erhalten und bestätigen sie hiermit.

+ +{{payment_info}} + +

Bestellübersicht

+{{order_items_table}} + +
+

+ {{shop_name}} | {{shop_address}}
+ Bei Fragen: {{shop_email}} +

+ + +HTML, + ], + [ + 'key' => 'order_shipped', + 'label' => 'Versandbestätigung', + 'subject' => 'Ihre Bestellung {{order_number}} ist unterwegs', + 'variables' => ['order_number','tracking_code','shop_name','shop_email'], + 'body' => <<<'HTML' + + + + +

Ihre Bestellung ist unterwegs!

+

Ihre Bestellung {{order_number}} wurde versendet.

+{{tracking_info}} +

Bei Fragen zu Ihrer Lieferung können Sie uns jederzeit kontaktieren.

+
+

{{shop_name}} | {{shop_email}}

+ + +HTML, + ], + [ + 'key' => 'payment_received', + 'label' => 'Zahlungsbestätigung', + 'subject' => 'Zahlung eingegangen – Bestellung {{order_number}}', + 'variables' => ['customer_name','order_number','order_total','shop_name','shop_email'], + 'body' => <<<'HTML' + + + + +

Zahlung eingegangen

+

Guten Tag {{customer_name}},

+

Ihre Zahlung für Bestellung {{order_number}} ({{order_total}} €) ist eingegangen.

+

Im Anhang finden Sie Ihre Rechnung als PDF.

+

Wir werden Ihre Bestellung schnellstmöglich bearbeiten und versenden.

+
+

{{shop_name}} | {{shop_email}}

+ + +HTML, + ], + [ + 'key' => 'widerruf_confirmation', + 'label' => 'Widerrufsbestätigung', + 'subject' => 'Bestätigung Ihres Widerrufs', + 'variables' => ['customer_name','widerruf_name','widerruf_email','widerruf_order_number','widerruf_product','widerruf_date','widerruf_reason','shop_name'], + 'body' => <<<'HTML' + + + + +

Bestätigung Ihres Widerrufs

+

Guten Tag {{customer_name}},

+

Ihr Widerruf ist bei uns eingegangen. Wir werden ihn schnellstmöglich bearbeiten.

+ + + + + + + + +
Name{{widerruf_name}}
E-Mail{{widerruf_email}}
Bestellnummer{{widerruf_order_number}}
Produkt{{widerruf_product}}
Datum{{widerruf_date}}
Begründung{{widerruf_reason}}
+ +

{{shop_name}}

+ + +HTML, + ], + ]; + + foreach ($templates as $tpl) { + EmailTemplate::updateOrCreate(['key' => $tpl['key']], $tpl); + } + } +} diff --git a/resources/views/admin/email-templates/edit.blade.php b/resources/views/admin/email-templates/edit.blade.php new file mode 100644 index 0000000..ef31878 --- /dev/null +++ b/resources/views/admin/email-templates/edit.blade.php @@ -0,0 +1,48 @@ +@extends('layouts.admin') +@section('title', 'E-Mail-Template: ' . $template->label) +@section('content') +
+ ← Zurück +

{{ $template->label }}

+
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +
+ @csrf @method('PUT') + +
+
+ + + @error('subject')

{{ $message }}

@enderror +
+ +
+ + + @error('body')

{{ $message }}

@enderror +
+
+ + @if($template->variables) +
+

Verfügbare Platzhalter:

+
+ @foreach($template->variables as $var) + {{"{{"}}{{ $var }}{{"}}"}} + @endforeach +
+
+ @endif + +
+ + Abbrechen +
+
+@endsection diff --git a/resources/views/admin/email-templates/index.blade.php b/resources/views/admin/email-templates/index.blade.php new file mode 100644 index 0000000..de6729f --- /dev/null +++ b/resources/views/admin/email-templates/index.blade.php @@ -0,0 +1,43 @@ +@extends('layouts.admin') +@section('title', 'E-Mail-Templates') +@section('content') +
+

E-Mail-Templates

+
+ +@if(session('success')) +
{{ session('success') }}
+@endif + +@if($templates->isEmpty()) +
+ Keine Templates gefunden. Bitte führen Sie php artisan db:seed --class=EmailTemplateSeeder aus. +
+@else +
+ + + + + + + + + + + @foreach($templates as $tpl) + + + + + + + @endforeach + +
TemplateBetreffZuletzt geändert
{{ $tpl->label }}{{ $tpl->subject }}{{ $tpl->updated_at->format('d.m.Y H:i') }} + Bearbeiten +
+
+@endif +@endsection diff --git a/resources/views/admin/pages/form.blade.php b/resources/views/admin/pages/form.blade.php index 64f32a3..e44fd25 100644 --- a/resources/views/admin/pages/form.blade.php +++ b/resources/views/admin/pages/form.blade.php @@ -152,6 +152,14 @@ class="w-full border rounded px-3 py-2 text-sm" min="0">

Niedrigere Zahl = weiter links

+
+ + +
diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 31d51d9..cff38d5 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -29,6 +29,7 @@ FAQ
System
Admin-Benutzer + E-Mail-Templates Einstellungen
diff --git a/resources/views/layouts/shop.blade.php b/resources/views/layouts/shop.blade.php index 60d303a..286e239 100644 --- a/resources/views/layouts/shop.blade.php +++ b/resources/views/layouts/shop.blade.php @@ -6,6 +6,33 @@ @yield('title', \App\Models\Setting::get('shop_name', 'Shop')) @vite(['resources/css/app.css', 'resources/js/app.js']) + @php + $themes = config('themes'); + $themeName = \App\Models\Setting::get('theme_name', 'default'); + $theme = $themes[$themeName] ?? $themes['default']; + @endphp + @@ -15,7 +42,7 @@

Wir verwenden Cookies, um Ihnen die bestmögliche Nutzererfahrung zu bieten. Mit dem Klick auf „Akzeptieren" stimmen Sie der Verwendung aller Cookies zu. Mehr erfahren

- +