373 lines
12 KiB
PHP
373 lines
12 KiB
PHP
<?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');
|
|
}
|
|
}
|