Files
WitShop/resources/views/admin/pages/form.blade.php
T
marcoandClaude Sonnet 4.6 5dad8b9324
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 7s
Add email templates, page layouts, and shop themes (template system)
- 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 <noreply@anthropic.com>
2026-06-20 22:04:54 +02:00

261 lines
12 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.
@extends('layouts.admin')
@section('title', isset($page) ? 'Seite bearbeiten' : 'Neue Seite')
@push('head')
{{-- Quill WYSIWYG --}}
<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
<style>
.ql-container { font-size: 14px; min-height: 320px; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; }
.ql-toolbar { border-top-left-radius: 6px; border-top-right-radius: 6px; background: #f9fafb; }
.ql-editor { min-height: 320px; line-height: 1.7; }
.ql-editor h1 { font-size: 1.8em; font-weight: 700; margin-bottom: .5em; }
.ql-editor h2 { font-size: 1.4em; font-weight: 600; margin-bottom: .4em; }
.ql-editor h3 { font-size: 1.15em; font-weight: 600; margin-bottom: .3em; }
.ql-editor a { color: #2563eb; text-decoration: underline; }
.ql-editor blockquote { border-left: 4px solid #d1d5db; padding-left: 1em; color: #6b7280; font-style: italic; }
.ql-editor pre { background: #1e293b; color: #e2e8f0; padding: 1em; border-radius: 6px; font-family: monospace; overflow-x: auto; }
.ql-editor ul, .ql-editor ol { padding-left: 1.5em; }
.ql-editor img { max-width: 100%; border-radius: 4px; }
</style>
@endpush
@section('content')
<div class="max-w-4xl">
<form id="page-form"
method="POST"
action="{{ isset($page) ? route('admin.pages.update', $page) : route('admin.pages.store') }}"
enctype="multipart/form-data"
class="space-y-4">
@csrf @if(isset($page)) @method('PUT') @endif
{{-- Metadaten --}}
<div class="bg-white rounded shadow p-6 space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Titel *</label>
<input type="text" name="title" id="title-input"
value="{{ old('title', $page->title ?? '') }}"
class="w-full border rounded px-3 py-2 text-sm" required>
</div>
<div>
<label class="block text-sm font-medium mb-1">
Slug (URL)
<span class="text-xs text-gray-400 font-normal">– automatisch aus Titel</span>
</label>
<div class="flex items-center border rounded overflow-hidden">
<span class="px-2 py-2 bg-gray-50 text-gray-400 text-xs border-r select-none">/seite/</span>
<input type="text" name="slug" id="slug-input"
value="{{ old('slug', $page->slug ?? '') }}"
class="flex-1 px-3 py-2 text-sm font-mono focus:outline-none"
placeholder="wird-automatisch-generiert">
</div>
</div>
<div>
<label class="block text-sm font-medium mb-1">Typ</label>
<select name="type" class="w-full border rounded px-3 py-2 text-sm">
<option value="static" {{ old('type', $page->type ?? 'static') === 'static' ? 'selected' : '' }}>Statische Seite (Impressum, AGB, …)</option>
<option value="blog" {{ old('type', $page->type ?? '') === 'blog' ? 'selected' : '' }}>Blog-Beitrag</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Titelbild</label>
@if(isset($page) && $page->image)
<img src="{{ asset('storage/'.$page->image) }}" class="h-10 mb-1 rounded">
@endif
<input type="file" name="image" accept="image/*" class="text-sm">
</div>
</div>
</div>
{{-- WYSIWYG Editor --}}
<div class="bg-white rounded shadow overflow-hidden">
<div class="px-5 py-3 border-b flex items-center justify-between">
<span class="text-sm font-semibold text-gray-700">Inhalt</span>
<button type="button" id="toggle-source"
class="text-xs px-3 py-1 border rounded hover:bg-gray-50 text-gray-500">
HTML-Quellcode
</button>
</div>
{{-- Quill Editor --}}
<div id="quill-editor" class="border-0">
{!! old('content', $page->content ?? '') !!}
</div>
{{-- Roher HTML-Quellcode (Toggle) --}}
<textarea id="source-editor" name="_content_source" rows="16"
class="hidden w-full px-4 py-3 text-sm font-mono border-0 focus:outline-none resize-none"
style="min-height: 320px">{{ old('content', $page->content ?? '') }}</textarea>
{{-- Verstecktes Feld das beim Submit gefüllt wird --}}
<input type="hidden" name="content" id="content-hidden"
value="{{ old('content', $page->content ?? '') }}">
</div>
{{-- SEO & Optionen --}}
<div class="bg-white rounded shadow p-6 space-y-4">
<h3 class="text-sm font-semibold text-gray-700">SEO & Einstellungen</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-1">Meta-Titel <span class="text-xs text-gray-400 font-normal">(Browser-Tab)</span></label>
<input type="text" name="meta_title"
value="{{ old('meta_title', $page->meta_title ?? '') }}"
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium mb-1">Meta-Beschreibung <span class="text-xs text-gray-400 font-normal">(Suchergebnisse)</span></label>
<input type="text" name="meta_description"
value="{{ old('meta_description', $page->meta_description ?? '') }}"
class="w-full border rounded px-3 py-2 text-sm"
maxlength="160">
</div>
<div>
<label class="block text-sm font-medium mb-1">Veröffentlichungsdatum</label>
<input type="date" name="published_at"
value="{{ old('published_at', isset($page->published_at) ? $page->published_at->format('Y-m-d') : '') }}"
class="w-full border rounded px-3 py-2 text-sm">
</div>
<div class="flex items-end pb-2">
<label class="flex items-center gap-2 text-sm cursor-pointer">
<input type="checkbox" name="published" value="1"
{{ old('published', $page->published ?? true) ? 'checked' : '' }}
class="w-4 h-4 accent-blue-600">
<span>Veröffentlicht</span>
</label>
</div>
</div>
<hr class="border-gray-100">
<h3 class="text-sm font-semibold text-gray-700">Navigation</h3>
<div class="grid grid-cols-3 gap-4">
<label class="flex items-start gap-2 text-sm cursor-pointer p-3 border rounded-lg hover:bg-gray-50">
<input type="checkbox" name="show_in_footer" value="1"
{{ old('show_in_footer', $page->show_in_footer ?? true) ? 'checked' : '' }}
class="mt-0.5 accent-blue-600">
<div>
<span class="font-medium">Footer</span>
<p class="text-xs text-gray-400 mt-0.5">Link in der Fußzeile des Shops</p>
</div>
</label>
<label class="flex items-start gap-2 text-sm cursor-pointer p-3 border rounded-lg hover:bg-gray-50">
<input type="checkbox" name="show_in_nav" value="1"
{{ old('show_in_nav', $page->show_in_nav ?? false) ? 'checked' : '' }}
class="mt-0.5 accent-blue-600">
<div>
<span class="font-medium">Hauptmenü</span>
<p class="text-xs text-gray-400 mt-0.5">Link in der Top-Navigation</p>
</div>
</label>
<div>
<label class="block text-sm font-medium mb-1">Reihenfolge im Menü</label>
<input type="number" name="nav_sort" value="{{ old('nav_sort', $page->nav_sort ?? 0) }}"
class="w-full border rounded px-3 py-2 text-sm" min="0">
<p class="text-xs text-gray-400 mt-1">Niedrigere Zahl = weiter links</p>
</div>
<div>
<label class="block text-sm font-medium mb-1">Seiten-Layout</label>
<select name="layout" class="w-full border rounded px-3 py-2 text-sm">
@foreach(['default'=>'Standard (zentriert)','sidebar'=>'Mit Sidebar','full'=>'Volle Breite','landing'=>'Landing Page (ohne Header/Footer)'] as $val => $label)
<option value="{{ $val }}" {{ old('layout', $page->layout ?? 'default') === $val ? 'selected' : '' }}>{{ $label }}</option>
@endforeach
</select>
</div>
</div>
</div>
@if($errors->any())
<div class="bg-red-50 border border-red-200 rounded px-4 py-3 text-sm text-red-700">
@foreach($errors->all() as $e)<p>• {{ $e }}</p>@endforeach
</div>
@endif
<div class="flex gap-3 pb-8">
<button type="submit" class="bg-blue-600 text-white px-6 py-2.5 rounded-lg hover:bg-blue-700 text-sm font-medium">
Speichern
</button>
<a href="{{ route('admin.pages.index') }}"
class="px-6 py-2.5 rounded-lg border text-sm hover:bg-gray-50">
Abbrechen
</a>
</div>
</form>
</div>
<script src="https://cdn.quilljs.com/1.3.7/quill.js"></script>
<script>
// ── Quill initialisieren ────────────────────────────────────────────────────
const quill = new Quill('#quill-editor', {
theme: 'snow',
modules: {
toolbar: [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ color: [] }, { background: [] }],
[{ align: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ indent: '-1' }, { indent: '+1' }],
['blockquote', 'code-block'],
['link', 'image'],
['clean']
]
},
placeholder: 'Seiteninhalt hier eingeben …',
});
// ── Slug automatisch aus Titel generieren ───────────────────────────────────
const titleInput = document.getElementById('title-input');
const slugInput = document.getElementById('slug-input');
let slugManuallyEdited = slugInput.value.length > 0;
titleInput.addEventListener('input', () => {
if (slugManuallyEdited) return;
slugInput.value = titleInput.value
.toLowerCase()
.replace(/ä/g,'ae').replace(/ö/g,'oe').replace(/ü/g,'ue').replace(/ß/g,'ss')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
});
slugInput.addEventListener('input', () => { slugManuallyEdited = true; });
// ── HTML-Quellcode-Toggle ───────────────────────────────────────────────────
const toggleBtn = document.getElementById('toggle-source');
const sourceEditor = document.getElementById('source-editor');
const quillWrapper = document.getElementById('quill-editor');
let sourceMode = false;
toggleBtn.addEventListener('click', () => {
sourceMode = !sourceMode;
if (sourceMode) {
// Quill → HTML
sourceEditor.value = quill.root.innerHTML === '<p><br></p>' ? '' : quill.root.innerHTML;
quillWrapper.style.display = 'none';
sourceEditor.classList.remove('hidden');
toggleBtn.textContent = 'WYSIWYG';
toggleBtn.classList.add('bg-yellow-50', 'text-yellow-700', 'border-yellow-300');
} else {
// HTML → Quill
quill.root.innerHTML = sourceEditor.value;
sourceEditor.classList.add('hidden');
quillWrapper.style.display = '';
toggleBtn.textContent = 'HTML-Quellcode';
toggleBtn.classList.remove('bg-yellow-50', 'text-yellow-700', 'border-yellow-300');
}
});
// ── Beim Absenden: Quill-HTML in hidden field schreiben ─────────────────────
document.getElementById('page-form').addEventListener('submit', () => {
const html = sourceMode
? sourceEditor.value
: (quill.root.innerHTML === '<p><br></p>' ? '' : quill.root.innerHTML);
document.getElementById('content-hidden').value = html;
});
</script>
@push('styles')
<style>
/* Quill-Bilder klickbar im Editor */
.ql-editor img { cursor: pointer; }
</style>
@endpush
@endsection