366 lines
No EOL
16 KiB
PHP
366 lines
No EOL
16 KiB
PHP
<div class="space-y-6">
|
||
{{-- Header --}}
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h1 class="text-2xl font-semibold text-gray-900">Blog Manager</h1>
|
||
<p class="text-sm text-gray-500 mt-1">
|
||
Create and manage blog posts
|
||
</p>
|
||
</div>
|
||
|
||
<div class="flex items-center space-x-3">
|
||
<flux:button wire:click="createPost" variant="primary">
|
||
New Post
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Stats Cards --}}
|
||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
@foreach($this->getStats() as $stat)
|
||
<flux:card>
|
||
<div class="text-center p-4">
|
||
<div class="text-2xl font-bold text-gray-900">{{ $stat['value'] }}</div>
|
||
<div class="text-sm text-gray-500">{{ $stat['label'] }}</div>
|
||
</div>
|
||
</flux:card>
|
||
@endforeach
|
||
</div>
|
||
|
||
{{-- Filters --}}
|
||
<flux:card>
|
||
<div class="flex flex-wrap items-center gap-4">
|
||
<div class="flex-1 min-w-64">
|
||
<flux:input wire:model.live="search"
|
||
placeholder="Search posts..."
|
||
class="w-full" />
|
||
</div>
|
||
|
||
<flux:select wire:model.live="statusFilter" placeholder="All Status">
|
||
<flux:option value="">All Status</flux:option>
|
||
<flux:option value="published">Published</flux:option>
|
||
<flux:option value="draft">Draft</flux:option>
|
||
<flux:option value="featured">Featured</flux:option>
|
||
</flux:select>
|
||
|
||
<flux:select wire:model.live="categoryFilter" placeholder="All Categories">
|
||
<flux:option value="">All Categories</flux:option>
|
||
@foreach($this->getCategories() as $category)
|
||
<flux:option value="{{ $category }}">{{ $category }}</flux:option>
|
||
@endforeach
|
||
</flux:select>
|
||
|
||
<flux:select wire:model.live="sortBy">
|
||
<flux:option value="created_at">Created Date</flux:option>
|
||
<flux:option value="published_at">Published Date</flux:option>
|
||
<flux:option value="title">Title</flux:option>
|
||
</flux:select>
|
||
|
||
<flux:select wire:model.live="sortDirection">
|
||
<flux:option value="desc">Newest First</flux:option>
|
||
<flux:option value="asc">Oldest First</flux:option>
|
||
</flux:select>
|
||
</div>
|
||
</flux:card>
|
||
|
||
{{-- Posts Table --}}
|
||
<flux:card>
|
||
<flux:table>
|
||
<flux:columns>
|
||
<flux:column>Title</flux:column>
|
||
<flux:column>Status</flux:column>
|
||
<flux:column>Category</flux:column>
|
||
<flux:column>Author</flux:column>
|
||
<flux:column>Published</flux:column>
|
||
<flux:column>Actions</flux:column>
|
||
</flux:columns>
|
||
|
||
<flux:rows>
|
||
@forelse($posts as $post)
|
||
<flux:row wire:key="post-{{ $post->id }}">
|
||
<flux:cell>
|
||
<div>
|
||
<div class="font-medium text-gray-900">
|
||
{{ $post->title }}
|
||
</div>
|
||
@if($post->excerpt)
|
||
<div class="text-sm text-gray-500 mt-1">
|
||
{{ str_limit($post->excerpt, 60) }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</flux:cell>
|
||
|
||
<flux:cell>
|
||
<div class="flex items-center space-x-2">
|
||
@if($post->is_published)
|
||
<flux:badge color="green" size="sm">Published</flux:badge>
|
||
@else
|
||
<flux:badge color="gray" size="sm">Draft</flux:badge>
|
||
@endif
|
||
|
||
@if($post->is_featured)
|
||
<flux:badge color="blue" size="sm">Featured</flux:badge>
|
||
@endif
|
||
</div>
|
||
</flux:cell>
|
||
|
||
<flux:cell>
|
||
{{ $post->category ?? '—' }}
|
||
</flux:cell>
|
||
|
||
<flux:cell>
|
||
{{ $post->author?->name ?? '—' }}
|
||
</flux:cell>
|
||
|
||
<flux:cell>
|
||
{{ $post->published_at?->format('M j, Y') ?? '—' }}
|
||
</flux:cell>
|
||
|
||
<flux:cell>
|
||
<div class="flex items-center space-x-2">
|
||
@if($post->is_published)
|
||
<flux:button href="{{ $post->getUrl() }}"
|
||
target="_blank"
|
||
size="sm"
|
||
variant="ghost">
|
||
View
|
||
</flux:button>
|
||
@endif
|
||
|
||
<flux:button wire:click="editPost({{ $post->id }})"
|
||
size="sm"
|
||
variant="ghost">
|
||
Edit
|
||
</flux:button>
|
||
|
||
<flux:button wire:click="duplicatePost({{ $post->id }})"
|
||
size="sm"
|
||
variant="ghost">
|
||
Duplicate
|
||
</flux:button>
|
||
|
||
<flux:button wire:click="deletePost({{ $post->id }})"
|
||
size="sm"
|
||
variant="danger">
|
||
Delete
|
||
</flux:button>
|
||
</div>
|
||
</flux:cell>
|
||
</flux:row>
|
||
@empty
|
||
<flux:row>
|
||
<flux:cell colspan="6">
|
||
<div class="text-center py-8 text-gray-500">
|
||
<flux:icon.document-text class="w-12 h-12 mx-auto mb-3 text-gray-300" />
|
||
<p>No blog posts found.</p>
|
||
<p class="text-sm">Create your first blog post to get started.</p>
|
||
</div>
|
||
</flux:cell>
|
||
</flux:row>
|
||
@endforelse
|
||
</flux:rows>
|
||
</flux:table>
|
||
|
||
{{-- Pagination --}}
|
||
@if($posts->hasPages())
|
||
<div class="px-6 py-4 border-t border-gray-200">
|
||
{{ $posts->links() }}
|
||
</div>
|
||
@endif
|
||
</flux:card>
|
||
|
||
{{-- Post Form Modal --}}
|
||
<flux:modal name="post-form" class="md:w-6xl max-h-screen overflow-y-auto">
|
||
<div class="space-y-6">
|
||
<div class="flex items-center justify-between">
|
||
<flux:heading size="lg">
|
||
{{ $editingPost ? 'Edit Post' : 'Create Post' }}
|
||
</flux:heading>
|
||
|
||
<div class="flex items-center space-x-3">
|
||
@if($editingPost)
|
||
<flux:button wire:click="saveAsDraft" variant="ghost">
|
||
Save as Draft
|
||
</flux:button>
|
||
<flux:button wire:click="publishPost" variant="primary">
|
||
{{ $editingPost->is_published ? 'Update' : 'Publish' }}
|
||
</flux:button>
|
||
@else
|
||
<flux:button wire:click="saveDraft" variant="ghost">
|
||
Save Draft
|
||
</flux:button>
|
||
<flux:button wire:click="saveAndPublish" variant="primary">
|
||
Publish
|
||
</flux:button>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Language Tabs --}}
|
||
<div class="border-b border-gray-200">
|
||
<nav class="-mb-px flex space-x-8">
|
||
@foreach($availableLocales as $locale)
|
||
<button wire:click="setActiveLocale('{{ $locale }}')"
|
||
class="py-2 px-1 border-b-2 font-medium text-sm {{ $activeLocale === $locale ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||
{{ strtoupper($locale) }}
|
||
</button>
|
||
@endforeach
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
{{-- Main Content --}}
|
||
<div class="lg:col-span-2 space-y-6">
|
||
{{-- Title & Slug --}}
|
||
<div class="space-y-4">
|
||
<flux:field>
|
||
<flux:label>Title ({{ strtoupper($activeLocale) }})</flux:label>
|
||
<flux:input wire:model.live="postForm.title.{{ $activeLocale }}"
|
||
placeholder="Enter post title" />
|
||
<flux:error name="postForm.title.{{ $activeLocale }}" />
|
||
</flux:field>
|
||
|
||
<flux:field>
|
||
<flux:label>Slug ({{ strtoupper($activeLocale) }})</flux:label>
|
||
<flux:input wire:model.live="postForm.slug.{{ $activeLocale }}"
|
||
placeholder="post-url-slug" />
|
||
<flux:error name="postForm.slug.{{ $activeLocale }}" />
|
||
</flux:field>
|
||
</div>
|
||
|
||
{{-- Excerpt --}}
|
||
<flux:field>
|
||
<flux:label>Excerpt ({{ strtoupper($activeLocale) }})</flux:label>
|
||
<flux:textarea wire:model.live="postForm.excerpt.{{ $activeLocale }}"
|
||
rows="3"
|
||
placeholder="Brief description of the post" />
|
||
<flux:description>Optional excerpt for post listings and SEO</flux:description>
|
||
<flux:error name="postForm.excerpt.{{ $activeLocale }}" />
|
||
</flux:field>
|
||
|
||
{{-- Content --}}
|
||
<flux:field>
|
||
<flux:label>Content ({{ strtoupper($activeLocale) }})</flux:label>
|
||
<div wire:ignore>
|
||
<textarea wire:model.live="postForm.content.{{ $activeLocale }}"
|
||
class="wysiwyg-editor"
|
||
rows="15"
|
||
placeholder="Write your post content here..."></textarea>
|
||
</div>
|
||
<flux:error name="postForm.content.{{ $activeLocale }}" />
|
||
</flux:field>
|
||
</div>
|
||
|
||
{{-- Sidebar --}}
|
||
<div class="space-y-6">
|
||
{{-- Publishing Options --}}
|
||
<flux:card>
|
||
<flux:card.header>
|
||
<flux:heading size="sm">Publishing</flux:heading>
|
||
</flux:card.header>
|
||
|
||
<div class="space-y-4">
|
||
<flux:field>
|
||
<flux:label>Published Date</flux:label>
|
||
<flux:input wire:model.live="postForm.published_at"
|
||
type="datetime-local" />
|
||
</flux:field>
|
||
|
||
<flux:field>
|
||
<flux:checkbox wire:model.live="postForm.is_featured">
|
||
Featured Post
|
||
</flux:checkbox>
|
||
<flux:description>Featured posts appear prominently on the site</flux:description>
|
||
</flux:field>
|
||
</div>
|
||
</flux:card>
|
||
|
||
{{-- Categories & Tags --}}
|
||
<flux:card>
|
||
<flux:card.header>
|
||
<flux:heading size="sm">Organization</flux:heading>
|
||
</flux:card.header>
|
||
|
||
<div class="space-y-4">
|
||
<flux:field>
|
||
<flux:label>Category</flux:label>
|
||
<flux:input wire:model.live="postForm.category"
|
||
placeholder="e.g., Technology, News" />
|
||
</flux:field>
|
||
|
||
<flux:field>
|
||
<flux:label>Tags</flux:label>
|
||
<flux:input wire:model.live="tagsInput"
|
||
placeholder="Enter tags separated by commas" />
|
||
<flux:description>Separate multiple tags with commas</flux:description>
|
||
</flux:field>
|
||
</div>
|
||
</flux:card>
|
||
|
||
{{-- SEO Settings --}}
|
||
<flux:card>
|
||
<flux:card.header>
|
||
<flux:heading size="sm">SEO Settings</flux:heading>
|
||
</flux:card.header>
|
||
|
||
<div class="space-y-4">
|
||
<flux:field>
|
||
<flux:label>Meta Title ({{ strtoupper($activeLocale) }})</flux:label>
|
||
<flux:input wire:model.live="postForm.meta_title.{{ $activeLocale }}"
|
||
placeholder="SEO title (defaults to post title)" />
|
||
</flux:field>
|
||
|
||
<flux:field>
|
||
<flux:label>Meta Description ({{ strtoupper($activeLocale) }})</flux:label>
|
||
<flux:textarea wire:model.live="postForm.meta_description.{{ $activeLocale }}"
|
||
rows="3"
|
||
placeholder="SEO description (defaults to excerpt)" />
|
||
</flux:field>
|
||
</div>
|
||
</flux:card>
|
||
|
||
{{-- Featured Image --}}
|
||
<flux:card>
|
||
<flux:card.header>
|
||
<flux:heading size="sm">Featured Image</flux:heading>
|
||
</flux:card.header>
|
||
|
||
<div class="space-y-4">
|
||
@if($featuredImage)
|
||
<div class="relative">
|
||
<img src="{{ $featuredImage->getUrl('thumb') }}"
|
||
alt="Featured image"
|
||
class="w-full rounded">
|
||
<button wire:click="removeFeaturedImage"
|
||
class="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs">
|
||
×
|
||
</button>
|
||
</div>
|
||
@else
|
||
<flux:button wire:click="selectFeaturedImage" size="sm" class="w-full">
|
||
Select Featured Image
|
||
</flux:button>
|
||
@endif
|
||
</div>
|
||
</flux:card>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-end space-x-3 pt-6 border-t border-gray-200">
|
||
<flux:button wire:click="$dispatch('close-modal')" variant="ghost">
|
||
Cancel
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
</flux:modal>
|
||
</div>
|
||
|
||
@push('scripts')
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Initialize WYSIWYG editor for content
|
||
// You can integrate your preferred editor here (CKEditor, TinyMCE, Quill, etc.)
|
||
});
|
||
</script>
|
||
@endpush |