Initial commit
This commit is contained in:
372
app/Http/Livewire/ReserveButton.php
Normal file
372
app/Http/Livewire/ReserveButton.php
Normal file
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Livewire\Component;
|
||||
use Namu\WireChat\Events\NotifyParticipant;
|
||||
use WireUi\Traits\WireUiActions;
|
||||
|
||||
class ReserveButton extends Component
|
||||
{
|
||||
use WireUiActions;
|
||||
public $post;
|
||||
public $activeProfile;
|
||||
public $hasReserved = false;
|
||||
public $reservationCount = 0;
|
||||
public $canReserve = false;
|
||||
public $disabledReason = null;
|
||||
public $showConfirmModal = false;
|
||||
public $showCancelModal = false;
|
||||
public $showReservationsModal = false;
|
||||
public $reservationsByType = [];
|
||||
public $isOrganizer = false;
|
||||
public $messageToReserved = '';
|
||||
public $isGuest = false;
|
||||
|
||||
public function mount($post)
|
||||
{
|
||||
$this->post = $post;
|
||||
$this->activeProfile = getActiveProfile();
|
||||
$this->isGuest = !$this->activeProfile;
|
||||
|
||||
// Ensure meeting is loaded
|
||||
if (!$this->post->relationLoaded('meeting')) {
|
||||
$this->post->load('meeting');
|
||||
}
|
||||
|
||||
// Get reservation count
|
||||
if ($this->post && method_exists($this->post, 'loveReactant')) {
|
||||
$reactant = $this->post->getLoveReactant();
|
||||
if ($reactant) {
|
||||
$reactionType = \Cog\Laravel\Love\ReactionType\Models\ReactionType::fromName('Reserve');
|
||||
if ($reactionType) {
|
||||
$this->reservationCount = $reactant->getReactionCounterOfType($reactionType)?->count ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkReservationPermissions();
|
||||
$this->checkIfReserved();
|
||||
$this->loadReservations();
|
||||
}
|
||||
|
||||
private function loadReservations()
|
||||
{
|
||||
// Only load if current profile is the organizer of the meeting
|
||||
if (!$this->post || !isset($this->post->meeting)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->isOrganizer = session('activeProfileType') === $this->post->meeting->meetingable_type
|
||||
&& session('activeProfileId') === $this->post->meeting->meetingable_id;
|
||||
|
||||
if (!$this->isOrganizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all reacters for this post with Reserve reaction
|
||||
$reactionType = \Cog\Laravel\Love\ReactionType\Models\ReactionType::fromName('Reserve');
|
||||
if (!$reactionType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$reactions = \Cog\Laravel\Love\Reaction\Models\Reaction::where('reactant_id', $this->post->love_reactant_id)
|
||||
->where('reaction_type_id', $reactionType->getId())
|
||||
->get();
|
||||
|
||||
$groupedReactions = [
|
||||
'App\\Models\\User' => [],
|
||||
'App\\Models\\Organization' => [],
|
||||
'App\\Models\\Bank' => [],
|
||||
'App\\Models\\Admin' => [],
|
||||
];
|
||||
|
||||
foreach ($reactions as $reaction) {
|
||||
$reacter = $reaction->reacter;
|
||||
if ($reacter) {
|
||||
$reacterable = $reacter->reacterable;
|
||||
if ($reacterable) {
|
||||
$type = get_class($reacterable);
|
||||
if (isset($groupedReactions[$type])) {
|
||||
$groupedReactions[$type][] = $reacterable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->reservationsByType = $groupedReactions;
|
||||
}
|
||||
|
||||
private function checkReservationPermissions()
|
||||
{
|
||||
// Check if reserve reaction is enabled in config
|
||||
if (!timebank_config('reactions.reserve.enabled', false)) {
|
||||
$this->canReserve = false;
|
||||
$this->disabledReason = 'reaction_disabled';
|
||||
return;
|
||||
}
|
||||
|
||||
// Admins cannot reserve
|
||||
if ($this->activeProfile instanceof \App\Models\Admin) {
|
||||
$this->canReserve = false;
|
||||
$this->disabledReason = 'admin_cannot_reserve';
|
||||
return;
|
||||
}
|
||||
|
||||
// Profile must be registered as love reacter
|
||||
if (
|
||||
!$this->activeProfile ||
|
||||
!method_exists($this->activeProfile, 'isRegisteredAsLoveReacter') ||
|
||||
!$this->activeProfile->isRegisteredAsLoveReacter()
|
||||
) {
|
||||
$this->canReserve = false;
|
||||
$this->disabledReason = 'not_registered_reacter';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if post has a meeting
|
||||
if (!$this->post || !isset($this->post->meeting)) {
|
||||
$this->canReserve = false;
|
||||
$this->disabledReason = 'no_meeting';
|
||||
return;
|
||||
}
|
||||
|
||||
$this->canReserve = true;
|
||||
$this->disabledReason = null;
|
||||
}
|
||||
|
||||
private function checkIfReserved()
|
||||
{
|
||||
if (!$this->canReserve || !$this->activeProfile->isRegisteredAsLoveReacter()) {
|
||||
$this->hasReserved = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->hasReserved = $this->activeProfile
|
||||
->viaLoveReacter()
|
||||
->hasReactedTo($this->post, 'Reserve');
|
||||
}
|
||||
|
||||
public function redirectToLogin()
|
||||
{
|
||||
// Get the referrer URL (the page the user is viewing)
|
||||
$intendedUrl = request()->header('Referer');
|
||||
|
||||
// Store as intended URL
|
||||
if ($intendedUrl) {
|
||||
session(['url.intended' => $intendedUrl]);
|
||||
}
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
public function openConfirmModal()
|
||||
{
|
||||
$this->showConfirmModal = true;
|
||||
}
|
||||
|
||||
public function openCancelModal()
|
||||
{
|
||||
$this->showCancelModal = true;
|
||||
}
|
||||
|
||||
public function openReservationsModal()
|
||||
{
|
||||
// Security check: only organizer can view
|
||||
if (!$this->isOrganizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->showReservationsModal = true;
|
||||
}
|
||||
|
||||
public function confirmReservation()
|
||||
{
|
||||
$this->showConfirmModal = false;
|
||||
|
||||
if (!$this->canReserveSecurityCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->hasReserved) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->activeProfile->viaLoveReacter()->reactTo($this->post, 'Reserve');
|
||||
|
||||
// Update state
|
||||
$this->hasReserved = true;
|
||||
|
||||
// Refresh count from database
|
||||
$reactionType = \Cog\Laravel\Love\ReactionType\Models\ReactionType::fromName('Reserve');
|
||||
if ($reactionType) {
|
||||
$this->reservationCount = $this->post->getLoveReactant()->getReactionCounterOfType($reactionType)?->count ?? 0;
|
||||
}
|
||||
|
||||
\Log::info('Reservation added. hasReserved: ' . ($this->hasReserved ? 'true' : 'false') . ', count: ' . $this->reservationCount);
|
||||
|
||||
// Reload reservations list
|
||||
$this->loadReservations();
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Failed to add reservation: ' . $e->getMessage());
|
||||
session()->flash('error', __('Failed to add reservation. Please try again.'));
|
||||
}
|
||||
}
|
||||
|
||||
public function confirmCancellation()
|
||||
{
|
||||
$this->showCancelModal = false;
|
||||
|
||||
if (!$this->canReserveSecurityCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->hasReserved) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->activeProfile->viaLoveReacter()->unReactTo($this->post, 'Reserve');
|
||||
$this->reservationCount = max(0, $this->reservationCount - 1);
|
||||
$this->hasReserved = false;
|
||||
|
||||
// Reload reservations list
|
||||
$this->loadReservations();
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Failed to remove reservation: ' . $e->getMessage());
|
||||
session()->flash('error', __('Failed to cancel reservation. Please try again.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function canReserveSecurityCheck()
|
||||
{
|
||||
if (!$this->post || !isset($this->post->meeting)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!timebank_config('reactions.reserve.enabled', false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->activeProfile instanceof \App\Models\Admin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!method_exists($this->activeProfile, 'isRegisteredAsLoveReacter') ||
|
||||
!$this->activeProfile->isRegisteredAsLoveReacter()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function sendMessageToReserved()
|
||||
{
|
||||
// Security check: only organizer can send messages
|
||||
if (!$this->isOrganizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the message
|
||||
$this->validate([
|
||||
'messageToReserved' => 'required|string|max:300',
|
||||
]);
|
||||
|
||||
// Strip HTML tags for security
|
||||
$cleanMessage = strip_tags($this->messageToReserved);
|
||||
|
||||
// Get the send delay configuration (same as bulk mail)
|
||||
$sendDelay = timebank_config('bulk_mail.send_delay_seconds', 2);
|
||||
|
||||
// Get all reserved participants
|
||||
$allReserved = [];
|
||||
foreach ($this->reservationsByType as $type => $reacters) {
|
||||
$allReserved = array_merge($allReserved, $reacters);
|
||||
}
|
||||
|
||||
// Get the organizer profile
|
||||
$organizer = getActiveProfile();
|
||||
|
||||
// Create group chat with all participants
|
||||
// Get organizer's locale for group name
|
||||
$groupLocale = $organizer->lang_preference ?? config('app.fallback_locale');
|
||||
|
||||
// Get post title for group name with fallback logic
|
||||
$groupPostTranslation = $this->post->translations->where('locale', $groupLocale)->first();
|
||||
if (!$groupPostTranslation) {
|
||||
$fallbackLocale = config('app.fallback_locale');
|
||||
$groupPostTranslation = $this->post->translations->where('locale', $fallbackLocale)->first();
|
||||
}
|
||||
if (!$groupPostTranslation) {
|
||||
$groupPostTranslation = $this->post->translations->first();
|
||||
}
|
||||
$groupName = $groupPostTranslation ? $groupPostTranslation->title : 'Event ' . $this->post->id;
|
||||
|
||||
// Create group conversation
|
||||
$groupConversation = $organizer->createGroup(
|
||||
name: $groupName,
|
||||
description: trans('messages.Reservation_update', [], $groupLocale)
|
||||
);
|
||||
|
||||
// Add all participants to the group (skip organizer as they're already added)
|
||||
foreach ($allReserved as $reacter) {
|
||||
// Skip if this is the organizer (already added when creating group)
|
||||
if (get_class($reacter) === get_class($organizer) && $reacter->id === $organizer->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupConversation->addParticipant($reacter);
|
||||
}
|
||||
|
||||
// Construct the chat message using organizer's locale
|
||||
$chatMessage = $groupName . ' ' . __('update', [], $groupLocale) . ':' . PHP_EOL . PHP_EOL . $cleanMessage;
|
||||
|
||||
// Send message to the group
|
||||
$message = $organizer->sendMessageTo($groupConversation, $chatMessage);
|
||||
|
||||
// Broadcast to all participants for real-time notifications
|
||||
foreach ($allReserved as $reacter) {
|
||||
broadcast(new NotifyParticipant($reacter, $message));
|
||||
}
|
||||
|
||||
// Dispatch email jobs with delays
|
||||
$delay = 0;
|
||||
foreach ($allReserved as $reacter) {
|
||||
\App\Jobs\SendReservationUpdateMail::dispatch($reacter, $this->post, $cleanMessage, $organizer)
|
||||
->delay(now()->addSeconds($delay))
|
||||
->onQueue('emails');
|
||||
|
||||
$delay += $sendDelay;
|
||||
}
|
||||
|
||||
// Also send a copy to the organizer
|
||||
\App\Jobs\SendReservationUpdateMail::dispatch($organizer, $this->post, $cleanMessage, $organizer)
|
||||
->delay(now()->addSeconds($delay))
|
||||
->onQueue('emails');
|
||||
|
||||
\Log::info('Reservation update messages dispatched', [
|
||||
'post_id' => $this->post->id,
|
||||
'organizer_id' => $organizer->id,
|
||||
'recipients_count' => count($allReserved) + 1, // +1 for organizer
|
||||
'group_conversation_id' => $groupConversation->id
|
||||
]);
|
||||
|
||||
// Clear the message and show success
|
||||
$this->messageToReserved = '';
|
||||
$this->showReservationsModal = false;
|
||||
|
||||
$this->notification()->success(
|
||||
__('Reservation update sent!'),
|
||||
__('All participants have been notified by email and chat message.')
|
||||
);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.reserve-button');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user