libra/resources/views/livewire/pages/posts/index.blade.php

144 lines
4.7 KiB
PHP

<?php
use App\Models\Post;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new #[Layout('components.layouts.public')] class extends Component
{
use WithPagination;
public string $search = '';
public function updatedSearch(): void
{
$this->resetPage();
}
public function clearSearch(): void
{
$this->search = '';
$this->resetPage();
}
public function highlightSearch(string $text, string $search): string
{
if (empty($search)) {
return e($text);
}
$escapedText = e($text);
$escapedSearch = e($search);
return preg_replace(
'/('.preg_quote($escapedSearch, '/').')/iu',
'<mark class="bg-gold/30 rounded px-1">$1</mark>',
$escapedText
);
}
public function with(): array
{
return [
'posts' => Post::published()
->when($this->search, function ($query) {
$search = $this->search;
$query->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('body', 'like', "%{$search}%");
});
})
->latest()
->paginate(10),
];
}
}; ?>
<div class="max-w-4xl mx-auto">
<flux:heading size="xl" class="text-navy">{{ __('posts.posts') }}</flux:heading>
<!-- Search Bar -->
<div class="mt-6 relative">
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="{{ __('posts.search_placeholder') }}"
class="w-full"
>
<x-slot:iconLeading>
<flux:icon name="magnifying-glass" class="w-5 h-5 text-charcoal/50" />
</x-slot:iconLeading>
</flux:input>
@if($search)
<button
wire:click="clearSearch"
class="absolute end-3 top-1/2 -translate-y-1/2 text-charcoal/50 hover:text-charcoal"
>
<flux:icon name="x-mark" class="w-5 h-5" />
</button>
@endif
</div>
<!-- Search Results Info -->
@if($search)
<p class="mt-4 text-sm text-charcoal/70">
@if($posts->total() > 0)
{{ __('posts.search_results', ['count' => $posts->total(), 'query' => $search]) }}
@else
{{ __('posts.no_results', ['query' => $search]) }}
@endif
</p>
@endif
<!-- Posts List -->
<div class="mt-8 space-y-6">
@forelse($posts as $post)
<article wire:key="post-{{ $post->id }}" class="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition-shadow">
<h2 class="text-xl font-semibold text-navy">
<a href="{{ route('posts.show', $post) }}" class="hover:text-gold" wire:navigate>
@if($search)
{!! $this->highlightSearch($post->getTitle(), $search) !!}
@else
{{ $post->getTitle() }}
@endif
</a>
</h2>
<time class="text-sm text-charcoal/70 mt-2 block">
{{ $post->published_at?->translatedFormat('d F Y') ?? $post->created_at->translatedFormat('d F Y') }}
</time>
<p class="mt-3 text-charcoal">
@if($search)
{!! $this->highlightSearch($post->getExcerpt(), $search) !!}
@else
{{ $post->getExcerpt() }}
@endif
</p>
<a href="{{ route('posts.show', $post) }}" class="text-gold hover:underline mt-4 inline-block" wire:navigate>
{{ __('posts.read_more') }} &rarr;
</a>
</article>
@empty
<div class="text-center py-12 bg-white rounded-lg">
@if($search)
<flux:icon name="magnifying-glass" class="w-12 h-12 mx-auto mb-4 text-charcoal/30" />
<p class="text-charcoal/70">{{ __('posts.no_results', ['query' => $search]) }}</p>
<flux:button wire:click="clearSearch" class="mt-4">
{{ __('posts.clear_search') }}
</flux:button>
@else
<flux:icon name="document-text" class="w-12 h-12 mx-auto mb-4 text-charcoal/40" />
<p class="text-charcoal/70">{{ __('posts.no_posts') }}</p>
@endif
</div>
@endforelse
</div>
<div class="mt-8">
{{ $posts->links() }}
</div>
</div>