Initial commit
This commit is contained in:
212
resources/views/livewire/reaction-button.blade.php
Normal file
212
resources/views/livewire/reaction-button.blade.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<div class="reaction-button-wrapper">
|
||||
<div class="reaction-button-container">
|
||||
@if ($shouldShow)
|
||||
@if ($canReact)
|
||||
<!-- User can react - show interactive button -->
|
||||
@if ($reactedByReactor)
|
||||
<!-- Active state - using the defined pattern -->
|
||||
<div class="reaction-container flex flex-col items-center">
|
||||
<button
|
||||
wire:click="unReact"
|
||||
onclick="event.stopPropagation();"
|
||||
class="reaction-button reaction-button--active group flex items-center space-x-1 p-2 rounded-lg transition-all duration-200 {{ $inverseColors ? 'text-white hover:text-gray-200 active:text-gray-300' : 'text-theme-reaction hover:text-theme-reaction-hover active:text-theme-primary' }}"
|
||||
title="{{ __('Click to remove :reaction', ['reaction' => $typeName]) }}"
|
||||
>
|
||||
<div class="reaction-icon {{ $size ?? 'w-10 h-10' }} transform transition-transform">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
{!! $iconSvg !!}
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@if ($showCounter)
|
||||
<div class="reaction-count-container flex items-center justify-center -mt-2">
|
||||
<span class="text-xs bg-transparent px-2 py-0.5 {{ $count > 0 ? ($inverseColors ? 'text-white' : 'text-theme-primary') : 'text-transparent' }}">
|
||||
{{ $count > 0 ? $count : '0' }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<!-- Inactive state - zero reactions -->
|
||||
<div class="reaction-container flex flex-col items-center">
|
||||
<button
|
||||
wire:click="react"
|
||||
onclick="event.stopPropagation();"
|
||||
class="reaction-button reaction-button--inactive group flex items-center space-x-1 p-2 rounded-lg transition-all duration-200 {{ $inverseColors ? 'text-gray-300 hover:text-white active:text-white' : 'text-theme-primary hover:text-theme-reaction active:text-theme-reaction' }}"
|
||||
title="{{ __('Click to :reaction', ['reaction' => $typeName]) }}"
|
||||
>
|
||||
<div class="reaction-icon {{ $size ?? 'w-10 h-10' }} transform transition-transform">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
{!! $iconSvg !!}
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@if ($showCounter)
|
||||
<div class="reaction-count-container flex items-center justify-center -mt-2">
|
||||
<span class="text-xs bg-transparent px-2 py-0.5 {{ $count > 0 ? ($inverseColors ? 'text-white' : 'text-theme-primary') : 'text-transparent' }}">
|
||||
{{ $count > 0 ? $count : '0' }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@elseif ($disabledReason === 'not_logged_in' && $redirectUrl)
|
||||
<!-- Guest: clickable button that redirects to login — solid icon when liked, hidden when no likes -->
|
||||
@if ($count > 0)
|
||||
<div class="reaction-container flex flex-col items-center">
|
||||
<a href="{{ route('login') }}?redirect={{ urlencode($redirectUrl) }}" onclick="event.stopPropagation();">
|
||||
<div class="reaction-button group flex items-center space-x-1 p-2 rounded-lg transition-all duration-200 {{ $inverseColors ? 'text-white hover:text-gray-200' : 'text-theme-primary hover:text-theme-reaction' }}">
|
||||
<div class="reaction-icon {{ $size ?? 'w-10 h-10' }} transform transition-transform">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
{!! $iconSvg !!}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@if ($showCounter)
|
||||
<div class="reaction-count-container flex items-center justify-center -mt-2">
|
||||
<span class="text-xs bg-transparent px-2 py-0.5 {{ $inverseColors ? 'text-white' : 'text-theme-primary' }}">
|
||||
{{ $count }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<!-- User cannot react - show disabled button with tooltip (only for specific reasons) -->
|
||||
@if ($disabledReasonText)
|
||||
<!-- Disabled with tooltip - applying the same pattern -->
|
||||
<div class="reaction-container flex flex-col items-center">
|
||||
<div class="reaction-button-disabled relative">
|
||||
<div
|
||||
class="reaction-button reaction-button--disabled group flex items-center space-x-1 p-2 rounded-lg cursor-pointer hover:text-theme-muted active:cursor-not-allowed select-none {{ $count > 0 ? 'text-theme-primary' : 'text-theme-primary' }}"
|
||||
onclick="event.stopPropagation(); this.nextElementSibling.classList.toggle('opacity-100'); this.nextElementSibling.classList.toggle('opacity-0'); setTimeout(() => { this.nextElementSibling.classList.add('opacity-0'); this.nextElementSibling.classList.remove('opacity-100'); }, 5000);"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown="if(event.key === 'Enter' || event.key === ' ') { event.preventDefault(); this.click(); }"
|
||||
>
|
||||
<div class="reaction-icon {{ $size ?? 'w-10 h-10' }} transform group-hover:scale-100 transition-transform">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="{{ $count > 0 ? 'currentColor' : 'none' }}" stroke="currentColor" stroke-width="1">
|
||||
{!! $iconSvg !!}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Click-activated tooltip -->
|
||||
<div class="absolute right-0 bottom-full mb-2 px-3 py-2 bg-theme-primary text-white text-base rounded-lg opacity-0 pointer-events-none transition-opacity duration-1000 whitespace-nowrap" style="z-index: 9999;">
|
||||
{{ __($disabledReasonText) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($showCounter)
|
||||
<div class="reaction-count-container flex items-center justify-center -mt-2">
|
||||
<span class="text-xs bg-transparent px-2 py-0.5 {{ $count > 0 ? ($inverseColors ? 'text-white' : 'text-theme-primary') : 'text-transparent' }}">
|
||||
{{ $count > 0 ? $count : '0' }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<!-- Show count only for other disabled reasons - hide entirely when no reactions -->
|
||||
@if ($showCounter && $count > 0)
|
||||
<div class="reaction-container flex flex-col items-center">
|
||||
<div class="reaction-display group flex items-center space-x-1 p-2">
|
||||
<div class="reaction-icon {{ $size ?? 'w-10 h-10' }} {{ $inverseColors ? 'text-white' : 'text-theme-primary' }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
{!! $iconSvg !!}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reaction-count-container flex items-center justify-center -mt-2">
|
||||
<span class="text-xs bg-transparent px-2 py-0.5 {{ $inverseColors ? 'text-white' : 'text-theme-primary' }}">
|
||||
{{ $count }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
{{-- Component is completely hidden if shouldShow is false --}}
|
||||
</div>
|
||||
|
||||
<!-- Reservation Confirmation Modal -->
|
||||
@if($typeName === 'reserve' && $targetModel && is_object($targetModel))
|
||||
<x-jetstream.dialog-modal wire:model.live="showConfirmModal" maxWidth="2xl">
|
||||
<x-slot name="title">
|
||||
{{ __('Confirm reservation') }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<div class="space-y-4">
|
||||
<p class="text-theme-primary">{{ __('Are you sure you want to reserve a spot for this event?') }}</p>
|
||||
|
||||
@if(method_exists($targetModel, 'translations') && $targetModel->translations->first())
|
||||
<div class="bg-theme-surface rounded-lg p-4 space-y-2">
|
||||
<div>
|
||||
<span class="font-semibold text-theme-primary">{{ __('Event') }}:</span>
|
||||
<span class="text-theme-primary">{{ $targetModel->translations->first()->title }}</span>
|
||||
</div>
|
||||
|
||||
@if(isset($targetModel->meeting))
|
||||
@if(isset($targetModel->meeting->venue))
|
||||
<div>
|
||||
<span class="font-semibold text-theme-primary">{{ __('Venue') }}:</span>
|
||||
<span class="text-theme-primary">{{ $targetModel->meeting->venue }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(isset($targetModel->meeting->address))
|
||||
<div>
|
||||
<span class="font-semibold text-theme-primary">{{ __('Address') }}:</span>
|
||||
<span class="text-theme-primary">{{ $targetModel->meeting->address }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(isset($targetModel->meeting->from))
|
||||
<div>
|
||||
<span class="font-semibold text-theme-primary">{{ __('Date & Time') }}:</span>
|
||||
<span class="text-theme-primary">{{ \Illuminate\Support\Carbon::parse($targetModel->meeting->from)->isoFormat('dddd D MMMM YYYY, H:mm') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="footer">
|
||||
<x-jetstream.secondary-button no-spinner wire:click="$set('showConfirmModal', false)">
|
||||
{{ __('Cancel') }}
|
||||
</x-jetstream.secondary-button>
|
||||
|
||||
<x-jetstream.button wire:click="confirmReaction" class="ml-3 bg-theme-brand">
|
||||
{{ __('Confirm reservation') }}
|
||||
</x-jetstream.button>
|
||||
</x-slot>
|
||||
</x-jetstream.dialog-modal>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('livewire:init', () => {
|
||||
// Listen for reaction events
|
||||
Livewire.on('reaction-added', (data) => {});
|
||||
|
||||
Livewire.on('reaction-removed', (data) => {});
|
||||
|
||||
Livewire.on('reaction-error', (data) => {
|
||||
// Show error message to user
|
||||
if (data && data.message) {
|
||||
alert(data.message);
|
||||
} else if (typeof data === 'string') {
|
||||
alert(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user