First commit

This commit is contained in:
Kevin Adametz 2025-10-20 17:50:35 +02:00
commit 7cf3558ba7
12933 changed files with 1180047 additions and 0 deletions

View file

@ -0,0 +1,8 @@
@extends('flux-cms::admin.layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-4">Edit Blog Post</h1>
@livewire('flux-cms::blog-editor', ['post' => $post])
</div>
@endsection

View file

@ -0,0 +1,185 @@
@extends('flux-cms::admin.layouts.app')
@section('title', 'CMS Dashboard')
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">CMS Dashboard</h1>
<p class="mt-2 text-gray-600">Übersicht über Ihre Website-Inhalte</p>
</div>
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Alle Seiten</dt>
<dd class="text-lg font-medium text-gray-900">{{ $stats['pages'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Veröffentlichte Seiten</dt>
<dd class="text-lg font-medium text-gray-900">{{ $stats['published_pages'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-yellow-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Entwürfe</dt>
<dd class="text-lg font-medium text-gray-900">{{ $stats['draft_pages'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Blog Posts</dt>
<dd class="text-lg font-medium text-gray-900">{{ $stats['blog_posts'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">Veröffentlichte Posts</dt>
<dd class="text-lg font-medium text-gray-900">{{ $stats['published_posts'] }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="bg-white shadow rounded-lg mb-8">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Schnellaktionen</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<a href="{{ route('admin.cms.pages.create') }}" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Neue Seite erstellen
</a>
<a href="{{ route('admin.cms.blog') }}" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
</svg>
Blog verwalten
</a>
<a href="{{ route('admin.cms.components') }}" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
Komponenten
</a>
</div>
</div>
</div>
<!-- Recent Pages -->
<div class="bg-white shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Zuletzt bearbeitete Seiten</h3>
@if($recentPages->count() > 0)
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Titel</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Domain</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aktualisiert</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aktionen</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($recentPages as $page)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{{ $page->getTranslation('title', app()->getLocale()) }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $page->domain_key }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
@if($page->is_published)
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
Veröffentlicht
</span>
@else
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800">
Entwurf
</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ $page->updated_at->format('d.m.Y H:i') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="{{ route('admin.cms.pages.edit', $page) }}" class="text-blue-600 hover:text-blue-900">Bearbeiten</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-gray-500">Noch keine Seiten vorhanden.</p>
@endif
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Flux CMS') - {{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Styles -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Tailwind CSS (optional, remove if Tailwind already built via Vite) -->
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="font-sans antialiased bg-gray-100">
<div class="min-h-screen">
<!-- Navigation -->
<nav class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="flex-shrink-0 flex items-center">
<a href="{{ route('admin.cms.index') }}" class="text-xl font-bold text-gray-900">
Flux CMS
</a>
</div>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<a href="{{ route('admin.cms.index') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm {{ request()->routeIs('admin.cms.index') ? 'border-blue-500 text-gray-900' : '' }}">
Dashboard
</a>
<a href="{{ route('admin.cms.pages') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm {{ request()->routeIs('admin.cms.pages*') ? 'border-blue-500 text-gray-900' : '' }}">
Seiten
</a>
<a href="{{ route('admin.cms.blog') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm {{ request()->routeIs('admin.cms.blog*') ? 'border-blue-500 text-gray-900' : '' }}">
Blog
</a>
<a href="{{ route('admin.cms.media') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm {{ request()->routeIs('admin.cms.media*') ? 'border-blue-500 text-gray-900' : '' }}">
Medien
</a>
<a href="{{ route('admin.cms.components') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm {{ request()->routeIs('admin.cms.components*') ? 'border-blue-500 text-gray-900' : '' }}">
Komponenten
</a>
</div>
</div>
<!-- Right side -->
<div class="hidden sm:ml-6 sm:flex sm:items-center">
<!-- User dropdown -->
<div class="ml-3 relative" x-data="{ open: false }">
<div>
<button @click="open = !open" class="bg-white flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<span class="sr-only">Open user menu</span>
<div class="h-8 w-8 rounded-full bg-gray-300 flex items-center justify-center">
<span class="text-sm font-medium text-gray-700">
{{ substr(auth()->user()->name ?? 'U', 0, 1) }}
</span>
</div>
</button>
</div>
<div x-show="open" @click.away="open = false" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<a href="{{ route('admin.cms.index') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Dashboard</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Einstellungen</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Abmelden
</button>
</form>
</div>
</div>
</div>
<!-- Mobile menu button -->
<div class="-mr-2 flex items-center sm:hidden">
<button @click="mobileMenuOpen = !mobileMenuOpen" class="bg-white inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500">
<span class="sr-only">Open main menu</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile menu -->
<div x-show="mobileMenuOpen" class="sm:hidden" x-data="{ mobileMenuOpen: false }">
<div class="pt-2 pb-3 space-y-1">
<a href="{{ route('admin.cms.index') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('admin.cms.index') ? 'border-blue-500 text-gray-900' : '' }}">
Dashboard
</a>
<a href="{{ route('admin.cms.pages') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('admin.cms.pages*') ? 'border-blue-500 text-gray-900' : '' }}">
Seiten
</a>
<a href="{{ route('admin.cms.blog') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('admin.cms.blog*') ? 'border-blue-500 text-gray-900' : '' }}">
Blog
</a>
<a href="{{ route('admin.cms.media') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('admin.cms.media*') ? 'border-blue-500 text-gray-900' : '' }}">
Medien
</a>
<a href="{{ route('admin.cms.components') }}" class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 block pl-3 pr-4 py-2 border-l-4 text-base font-medium {{ request()->routeIs('admin.cms.components*') ? 'border-blue-500 text-gray-900' : '' }}">
Komponenten
</a>
</div>
</div>
</nav>
<!-- Page Content -->
<main>
@yield('content')
</main>
</div>
<!-- Flash Messages -->
@if(session('success'))
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 5000)" class="fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 5000)" class="fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50">
{{ session('error') }}
</div>
@endif
<!-- Scripts -->
@stack('scripts')
</body>
</html>

View file

@ -0,0 +1,34 @@
<div class="flux-cms-field {{ $field->getCssClasses($hasError) }}">
<label for="{{ $fieldId }}" class="block text-sm font-medium text-gray-700 mb-1">
{{ $field->getLabel() }}
@if($field->isRequired())
<span class="text-red-500">*</span>
@endif
@if($field->isTranslatable())
<span class="text-xs text-gray-500">({{ $locale }})</span>
@endif
</label>
<input
type="{{ $field->getInputType() }}"
id="{{ $fieldId }}"
name="{{ $field->getKey() }}"
wire:model="{{ $wireModel }}"
value="{{ $value }}"
@if($field->isRequired()) required @endif
@if($field->getMaxLength() > 0) maxlength="{{ $field->getMaxLength() }}" @endif
@if($field->getMinLength() > 0) minlength="{{ $field->getMinLength() }}" @endif
@if($field->getPattern()) pattern="{{ $field->getPattern() }}" @endif
@if($field->getPlaceholder()) placeholder="{{ $field->getPlaceholder() }}" @endif
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm @if($hasError) border-red-300 @endif"
{!! $field->getAttributes() ? ' ' . implode(' ', array_map(fn($k, $v) => "{$k}=\"{$v}\"", array_keys($field->getAttributes()), $field->getAttributes())) : '' !!}
>
@if($field->getHelpText())
<p class="mt-1 text-sm text-gray-500">{{ $field->getHelpText() }}</p>
@endif
@if($hasError)
<p class="mt-1 text-sm text-red-600">{{ $error }}</p>
@endif
</div>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $page->getSeoTitle() }}</title>
<meta name="description" content="{{ $page->getSeoDescription() }}">
<meta name="keywords" content="{{ $page->getTranslation('meta_keywords', app()->getLocale()) }}">
<!-- Open Graph -->
<meta property="og:title" content="{{ $page->getTranslation('title', app()->getLocale()) }}">
<meta property="og:description" content="{{ $page->getSeoDescription() }}">
<meta property="og:url" content="{{ request()->url() }}">
<meta property="og:type" content="website">
@if($page->getTranslation('og_image', app()->getLocale()))
<meta property="og:image" content="{{ $page->getTranslation('og_image', app()->getLocale()) }}">
@endif
<!-- Canonical URL -->
<link rel="canonical" href="{{ $page->getCanonicalUrl() }}">
<!-- Styles -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Tailwind CSS (optional, remove if Tailwind already built via Vite) -->
<script src="https://cdn.tailwindcss.com"></script>
@stack('head')
</head>
<body class="font-sans antialiased">
<main class="cms-page">
@foreach($components as $component)
@if($component->canRender())
<div class="cms-component" data-component="{{ class_basename($component->component_class) }}">
@livewire($component->component_class, [
'content' => $component->getTranslations('content')
], key('component-' . $component->id))
</div>
@endif
@endforeach
</main>
<!-- Scripts -->
@stack('scripts')
</body>
</html>

View file

@ -0,0 +1,14 @@
User-agent: *
Allow: /
@if(config('flux-cms.seo.auto_sitemap', true))
Sitemap: {{ $baseUrl }}/sitemap.xml
@endif
# Disallow admin areas
Disallow: /admin/
Disallow: /preview/
# Disallow CMS specific paths
Disallow: /flux-cms/
Disallow: /vendor/

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@foreach($pages as $page)
@foreach(config('flux-cms.locales', ['de']) as $locale => $localeName)
<url>
<loc>{{ $page->getUrl($locale) }}</loc>
<lastmod>{{ $page->updated_at->format('Y-m-d') }}</lastmod>
<changefreq>weekly</changefreq>
<priority>{{ $page->getTranslation('slug', $locale) === '/' ? '1.0' : '0.8' }}</priority>
</url>
@endforeach
@endforeach
@foreach($blogPosts as $post)
@foreach(config('flux-cms.locales', ['de']) as $locale => $localeName)
<url>
<loc>{{ $post->getUrl($locale) }}</loc>
<lastmod>{{ $post->updated_at->format('Y-m-d') }}</lastmod>
<changefreq>monthly</changefreq>
<priority>{{ $post->is_featured ? '0.9' : '0.7' }}</priority>
</url>
@endforeach
@endforeach
</urlset>