'refresh', 'messageSent' => 'refresh', ]; public function mount() { if (Auth::check()) { $this->loadRecentConversations(); } } public function loadRecentConversations() { if (!Auth::check()) { return; } $service = new MessagingService(); // Get unread count $this->unreadCount = $service->getUnreadCount(Auth::id()); // Get all conversations (not limited) $conversations = $service->getUserConversations(Auth::id()) ->get(); $this->recentConversations = $conversations->map(function ($conversation) { $isPinned = $conversation->isPinnedForUser(Auth::id()); $result = [ 'id' => $conversation->id, 'type' => $conversation->type, 'last_message' => $conversation->lastMessage, 'unread_count' => $conversation->getUnreadCountForUser(Auth::id()), 'last_message_at' => $conversation->last_message_at, 'is_pinned' => $isPinned, ]; if ($conversation->type === 'group') { // For group conversations $participants = $conversation->participants()->limit(3)->get(); $result['subject'] = $conversation->subject; $result['created_by'] = $conversation->created_by; $result['is_creator'] = $conversation->created_by == Auth::id(); $result['participants_count'] = $conversation->participants()->count(); $result['participants'] = $participants->map(function ($user) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $user->id)->first(); return [ 'id' => $user->id, 'name' => $profile ? $profile->display_name : $user->name, 'avatar' => $profile ? $profile->avatar_url : $user->avatar, ]; })->toArray(); } elseif ($conversation->type === 'system') { // For system conversations $result['other_user'] = null; } else { // For direct conversations $otherUser = $conversation->getOtherParticipant(Auth::id()); // Load SocialBase profile if available $profile = null; if ($otherUser) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $otherUser->id)->first(); } $result['other_user'] = $otherUser ? [ 'id' => $otherUser->id, 'name' => $profile ? $profile->display_name : $otherUser->name, 'avatar' => $profile ? $profile->avatar_url : $otherUser->avatar, ] : null; } return $result; }) ->sort(function ($a, $b) { // Sort: system first, then pinned, then by last message date // System conversations always at the top if ($a['type'] === 'system' && $b['type'] !== 'system') return -1; if ($a['type'] !== 'system' && $b['type'] === 'system') return 1; // Then by pinned status if ($a['is_pinned'] && !$b['is_pinned']) return -1; if (!$a['is_pinned'] && $b['is_pinned']) return 1; // Finally by last message date $timeA = $a['last_message_at'] ?? now()->subYears(10); $timeB = $b['last_message_at'] ?? now()->subYears(10); return $timeB <=> $timeA; }) ->values() ->toArray(); } public function togglePin($conversationId) { $conversation = Conversation::findOrFail($conversationId); // Prevent unpinning system conversations if ($conversation->type === 'system') { return; } // Check if user is participant if (!$conversation->hasParticipant(Auth::id())) { return; } $conversation->togglePinForUser(Auth::id()); $this->loadRecentConversations(); } public function toggleBubble() { $this->isOpen = !$this->isOpen; if ($this->isOpen) { $this->loadRecentConversations(); } } public function selectConversation($conversationId) { $conversation = Conversation::findOrFail($conversationId); // Check if user is participant if (!$conversation->hasParticipant(Auth::id())) { return; } $this->selectedConversation = $conversationId; $this->loadMessages(); // Mark as read $conversation->markAsReadForUser(Auth::id()); $this->loadRecentConversations(); } public function loadMessages() { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation) { return; } $this->messages = $conversation->messages() ->with(['sender', 'reactions.user']) ->orderBy('created_at', 'asc') ->get() ->map(function ($message) { // Load SocialBase profile if available $profile = null; if ($message->sender) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $message->sender_id)->first(); } // Group reactions by type and get counts $reactionCounts = $message->reactions->groupBy('type')->map(function ($reactions, $type) { return [ 'emoji' => \Paymenter\Extensions\Others\SocialBase\Models\Reaction::TYPES[$type] ?? $type, 'count' => $reactions->count(), 'users' => $reactions->pluck('user.name')->toArray(), 'hasCurrentUser' => $reactions->contains('user_id', Auth::id()), ]; })->toArray(); return [ 'id' => $message->id, 'content' => $message->content, 'sender_id' => $message->sender_id, 'sender_name' => $message->sender ? ($profile ? $profile->display_name : $message->sender->name) : 'System', 'sender_avatar' => $message->sender ? ($profile ? $profile->avatar_url : $message->sender->avatar) : null, 'is_system' => $message->is_system, 'created_at' => $message->created_at, 'is_mine' => $message->sender_id === Auth::id(), 'reactions' => $reactionCounts, 'attachments' => $message->attachments ?? [], ]; }) ->values() ->toArray(); // Dispatch event to trigger scroll after messages are loaded $this->dispatch('messagesLoaded'); } public function sendMessage() { if (!Auth::check() || !$this->selectedConversation) { return; } // Block sending messages to system conversations $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type === 'system') { session()->flash('error', 'You cannot send messages to system conversations.'); return; } $this->validate([ 'newMessage' => 'required_without:attachments|string|max:2000', 'attachments.*' => 'file|max:10240|mimes:jpg,jpeg,png,gif,webp,mp4,mov,avi,txt,php,js,css,html,json,xml,md,zip', ]); // Process attachments $uploadedFiles = []; if (!empty($this->attachments)) { foreach ($this->attachments as $attachment) { $path = $attachment->store('socialbase/attachments', 'local'); $uploadedFiles[] = [ 'name' => $attachment->getClientOriginalName(), 'path' => $path, 'type' => $attachment->getMimeType(), 'size' => $attachment->getSize(), 'extension' => $attachment->getClientOriginalExtension(), ]; } } $message = Message::create([ 'conversation_id' => $this->selectedConversation, 'sender_id' => Auth::id(), 'content' => $this->newMessage ?? '', 'attachments' => !empty($uploadedFiles) ? $uploadedFiles : null, 'is_system' => false, ]); $this->newMessage = ''; $this->attachments = []; $this->loadMessages(); $this->loadRecentConversations(); // Mark conversation as read after sending $conversation->markAsReadForUser(Auth::id()); // Dispatch event for real-time updates $this->dispatch('messageSent'); } public function removeAttachment($index) { if (isset($this->attachments[$index])) { unset($this->attachments[$index]); $this->attachments = array_values($this->attachments); } } public function backToList() { $this->selectedConversation = null; $this->messages = []; } public function refresh() { $this->loadRecentConversations(); if ($this->selectedConversation) { $this->loadMessages(); } } public function deleteMessage($messageId) { $message = Message::find($messageId); if (!$message || $message->sender_id !== Auth::id()) { return; } try { $message->delete(); $this->loadMessages(); } catch (\Exception $e) { // Fail silently } } public function reactToMessage($messageId, $reaction) { $message = Message::find($messageId); if (!$message) { return; } try { $message->toggleReaction(Auth::user(), $reaction); $this->loadMessages(); } catch (\Exception $e) { // Fail silently } } public function openViewMembersModal() { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group') { return; } $this->groupMembers = $conversation->participants()->get()->map(function ($user) use ($conversation) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $user->id)->first(); return [ 'id' => $user->id, 'name' => $profile ? $profile->display_name : $user->name, 'email' => $user->email, 'avatar' => $profile ? $profile->avatar_url : $user->avatar, 'is_creator' => $user->id == $conversation->created_by, ]; })->toArray(); $this->showMembersModal = true; } public function closeViewMembersModal() { $this->showMembersModal = false; $this->groupMembers = []; } public function openAddMembersModal() { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group' || $conversation->created_by != Auth::id()) { return; } $existingMemberIds = $conversation->participants()->pluck('users.id')->toArray(); $this->availableUsers = \App\Models\User::where('id', '!=', Auth::id()) ->whereNotIn('id', $existingMemberIds) ->get() ->map(function ($user) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $user->id)->first(); return [ 'id' => $user->id, 'name' => $profile ? $profile->display_name : $user->name, 'avatar' => $profile ? $profile->avatar_url : $user->avatar, ]; }) ->toArray(); $this->selectedUsersToAdd = []; $this->showAddMembersModal = true; } public function closeAddMembersModal() { $this->showAddMembersModal = false; $this->availableUsers = []; $this->selectedUsersToAdd = []; } public function toggleUserToAdd($userId) { if (in_array($userId, $this->selectedUsersToAdd)) { $this->selectedUsersToAdd = array_values(array_diff($this->selectedUsersToAdd, [$userId])); } else { $this->selectedUsersToAdd[] = $userId; } } public function addMembersToGroup() { if (empty($this->selectedUsersToAdd)) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group' || $conversation->created_by != Auth::id()) { return; } foreach ($this->selectedUsersToAdd as $userId) { $conversation->participants()->attach($userId); // Send system message $user = \App\Models\User::find($userId); if ($user) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $userId)->first(); $displayName = $profile ? $profile->display_name : $user->name; Message::create([ 'conversation_id' => $conversation->id, 'sender_id' => null, 'content' => "**Member Added**\n\n" . Auth::user()->name . " added " . $displayName . " to the group", 'type' => 'system', 'is_system' => true, 'system_type' => 'member_added', 'metadata' => [], ]); } } $conversation->update(['last_message_at' => now()]); $this->closeAddMembersModal(); $this->loadRecentConversations(); $this->loadMessages(); } public function openRenameModal() { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group' || $conversation->created_by != Auth::id()) { return; } $this->newGroupName = $conversation->subject; $this->showRenameModal = true; } public function closeRenameModal() { $this->showRenameModal = false; $this->newGroupName = ''; } public function renameGroup() { $this->validate([ 'newGroupName' => 'required|string|max:255', ]); $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group' || $conversation->created_by != Auth::id()) { return; } $oldName = $conversation->subject; $conversation->update(['subject' => $this->newGroupName]); // Send system message Message::create([ 'conversation_id' => $conversation->id, 'sender_id' => null, 'content' => "**Group Renamed**\n\n" . Auth::user()->name . " renamed the group from \"" . $oldName . "\" to \"" . $this->newGroupName . "\"", 'type' => 'system', 'is_system' => true, 'system_type' => 'group_renamed', 'metadata' => [], ]); $conversation->update(['last_message_at' => now()]); $this->closeRenameModal(); $this->loadRecentConversations(); $this->loadMessages(); } public function leaveGroup() { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group') { return; } // Send system message before leaving $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', Auth::id())->first(); $displayName = $profile ? $profile->display_name : Auth::user()->name; Message::create([ 'conversation_id' => $conversation->id, 'sender_id' => null, 'content' => "**Member Left**\n\n" . $displayName . " left the group", 'type' => 'system', 'is_system' => true, 'system_type' => 'member_left', 'metadata' => [], ]); $conversation->update(['last_message_at' => now()]); // Remove user from conversation $conversation->participants()->detach(Auth::id()); // Reset selection $this->selectedConversation = null; $this->messages = []; $this->loadRecentConversations(); } public function removeMember($userId) { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); if (!$conversation || $conversation->type !== 'group' || $conversation->created_by != Auth::id()) { return; } // Can't remove creator if ($userId == $conversation->created_by) { return; } $user = \App\Models\User::find($userId); if (!$user) { return; } $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $userId)->first(); $displayName = $profile ? $profile->display_name : $user->name; // Send system message Message::create([ 'conversation_id' => $conversation->id, 'sender_id' => null, 'content' => "**Member Removed**\n\n" . Auth::user()->name . " removed " . $displayName . " from the group", 'type' => 'system', 'is_system' => true, 'system_type' => 'member_removed', 'metadata' => [], ]); $conversation->update(['last_message_at' => now()]); // Remove user from conversation $conversation->participants()->detach($userId); // Reload members modal $this->openViewMembersModal(); $this->loadMessages(); } public function render() { // Only render for authenticated users if (!Auth::check()) { return view('socialbase::empty'); } return view('socialbase::livewire.messaging-bubble'); } }