Files
thrillwiki_laravel/resources/views/livewire/photo-gallery-component.blade.php
pacnpal cc33781245 feat: Implement rides management with CRUD functionality
- Added rides index view with search and filter options.
- Created rides show view to display ride details.
- Implemented API routes for rides.
- Developed authentication routes for user registration, login, and email verification.
- Created tests for authentication, email verification, password reset, and user profile management.
- Added feature tests for rides and operators, including creation, updating, deletion, and searching.
- Implemented soft deletes and caching for rides and operators.
- Enhanced manufacturer and operator model tests for various functionalities.
2025-06-19 22:34:10 -04:00

299 lines
20 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.
<div>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-900">Photo Gallery</h3>
<div class="flex space-x-2">
<button
wire:click="toggleViewMode"
class="inline-flex items-center px-3 py-1.5 bg-gray-100 border border-gray-300 rounded-md font-medium text-xs text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition ease-in-out duration-150"
>
@if ($viewMode === 'grid')
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
</svg>
Carousel View
@else
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
Grid View
@endif
</button>
</div>
</div>
@if ($isLoading)
<div class="flex justify-center items-center py-12">
<svg class="animate-spin h-8 w-8 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
@elseif ($error)
<div class="bg-red-50 border border-red-200 text-red-800 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $error }}</span>
</div>
@elseif (count($photos) === 0)
<div class="text-center py-12 text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p class="text-lg font-medium">No photos yet</p>
<p class="mt-1">Upload photos to showcase this park.</p>
</div>
@else
@if ($viewMode === 'grid')
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
@foreach ($photos as $photo)
<div
wire:key="photo-{{ $photo->id }}"
class="relative group aspect-square overflow-hidden rounded-lg bg-gray-100"
>
<img
src="{{ $photo->url }}"
alt="{{ $photo->alt_text ?? $photo->title ?? 'Park photo' }}"
class="w-full h-full object-cover cursor-pointer"
wire:click="selectPhoto({{ $photo->id }})"
>
@if ($photo->is_featured)
<div class="absolute top-2 left-2 bg-yellow-500 text-white text-xs px-2 py-1 rounded-full">
Featured
</div>
@endif
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-40 transition-all duration-300 flex items-center justify-center opacity-0 group-hover:opacity-100">
<div class="flex space-x-2">
<button
wire:click="selectPhoto({{ $photo->id }})"
class="p-1.5 bg-white rounded-full text-gray-800 hover:bg-blue-500 hover:text-white transition-colors duration-200"
title="View photo"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</button>
@if (!$photo->is_featured)
<button
wire:click="setFeatured({{ $photo->id }})"
class="p-1.5 bg-white rounded-full text-gray-800 hover:bg-yellow-500 hover:text-white transition-colors duration-200"
title="Set as featured"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
</button>
@endif
<button
wire:click="deletePhoto({{ $photo->id }})"
wire:confirm="Are you sure you want to delete this photo? This action cannot be undone."
class="p-1.5 bg-white rounded-full text-gray-800 hover:bg-red-500 hover:text-white transition-colors duration-200"
title="Delete photo"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
</div>
@endforeach
</div>
@else
<div
x-data="{
activeSlide: 0,
totalSlides: {{ count($photos) }},
next() {
this.activeSlide = (this.activeSlide + 1) % this.totalSlides;
},
prev() {
this.activeSlide = (this.activeSlide - 1 + this.totalSlides) % this.totalSlides;
}
}"
class="relative"
>
<div class="relative aspect-video overflow-hidden rounded-lg bg-gray-100">
@foreach ($photos as $index => $photo)
<div
x-show="activeSlide === {{ $index }}"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform scale-95"
x-transition:enter-end="opacity-100 transform scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 transform scale-100"
x-transition:leave-end="opacity-0 transform scale-95"
class="absolute inset-0"
>
<img
src="{{ $photo->url }}"
alt="{{ $photo->alt_text ?? $photo->title ?? 'Park photo' }}"
class="w-full h-full object-contain"
>
@if ($photo->is_featured)
<div class="absolute top-4 left-4 bg-yellow-500 text-white px-2 py-1 rounded-full text-sm">
Featured
</div>
@endif
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-4 text-white">
<h4 class="font-medium">{{ $photo->title ?? 'Untitled Photo' }}</h4>
@if ($photo->description)
<p class="text-sm text-gray-200 mt-1">{{ $photo->description }}</p>
@endif
@if ($photo->credit)
<p class="text-xs text-gray-300 mt-2">Credit: {{ $photo->credit }}</p>
@endif
</div>
</div>
@endforeach
</div>
<button
@click="prev"
class="absolute left-2 top-1/2 -translate-y-1/2 p-1.5 bg-black/50 hover:bg-black/70 rounded-full text-white transition-colors duration-200"
>
<svg xmlns="http://www.w3.org/2000/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="M15 19l-7-7 7-7" />
</svg>
</button>
<button
@click="next"
class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 bg-black/50 hover:bg-black/70 rounded-full text-white transition-colors duration-200"
>
<svg xmlns="http://www.w3.org/2000/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="M9 5l7 7-7 7" />
</svg>
</button>
<div class="flex justify-center mt-4 space-x-2">
@foreach ($photos as $index => $photo)
<button
@click="activeSlide = {{ $index }}"
:class="{'bg-blue-600': activeSlide === {{ $index }}, 'bg-gray-300': activeSlide !== {{ $index }}}"
class="w-2.5 h-2.5 rounded-full transition-colors duration-200"
></button>
@endforeach
</div>
</div>
@endif
@endif
</div>
<!-- Photo Detail Modal -->
@if ($selectedPhoto)
<div
class="fixed inset-0 z-50 overflow-y-auto"
x-data="{}"
x-init="$nextTick(() => { document.body.style.overflow = 'hidden'; })"
x-on:keydown.escape.window="$wire.closePhotoDetail(); document.body.style.overflow = '';"
>
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-gray-900 bg-opacity-75 transition-opacity" wire:click="closePhotoDetail"></div>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
<div class="absolute top-0 right-0 pt-4 pr-4">
<button
wire:click="closePhotoDetail"
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<span class="sr-only">Close</span>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="p-6">
<div class="flex flex-col md:flex-row gap-6">
<div class="md:w-2/3">
<div class="aspect-video bg-gray-100 rounded-lg overflow-hidden">
<img
src="{{ $selectedPhoto->url }}"
alt="{{ $selectedPhoto->alt_text ?? $selectedPhoto->title ?? 'Park photo' }}"
class="w-full h-full object-contain"
>
</div>
</div>
<div class="md:w-1/3">
<h3 class="text-lg font-medium text-gray-900">
{{ $selectedPhoto->title ?? 'Untitled Photo' }}
</h3>
@if ($selectedPhoto->description)
<p class="mt-2 text-sm text-gray-500">
{{ $selectedPhoto->description }}
</p>
@endif
<div class="mt-4 border-t border-gray-200 pt-4">
<dl class="space-y-3 text-sm">
@if ($selectedPhoto->credit)
<div>
<dt class="font-medium text-gray-500">Credit:</dt>
<dd class="mt-1 text-gray-900">{{ $selectedPhoto->credit }}</dd>
</div>
@endif
@if ($selectedPhoto->source_url)
<div>
<dt class="font-medium text-gray-500">Source:</dt>
<dd class="mt-1 text-blue-600">
<a href="{{ $selectedPhoto->source_url }}" target="_blank" rel="noopener noreferrer" class="hover:underline">
{{ $selectedPhoto->source_url }}
</a>
</dd>
</div>
@endif
<div>
<dt class="font-medium text-gray-500">Dimensions:</dt>
<dd class="mt-1 text-gray-900">{{ $selectedPhoto->width }} × {{ $selectedPhoto->height }}</dd>
</div>
<div>
<dt class="font-medium text-gray-500">File Size:</dt>
<dd class="mt-1 text-gray-900">{{ number_format($selectedPhoto->file_size / 1024, 2) }} KB</dd>
</div>
</dl>
</div>
<div class="mt-6 flex space-x-3">
@if (!$selectedPhoto->is_featured)
<button
wire:click="setFeatured({{ $selectedPhoto->id }})"
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
Set as Featured
</button>
@endif
<button
wire:click="deletePhoto({{ $selectedPhoto->id }})"
wire:confirm="Are you sure you want to delete this photo? This action cannot be undone."
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete Photo
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endif
</div>