'loadMessages', 'messageSent' => 'refreshAll', 'groupCreated' => 'handleGroupCreated', ]; protected $rules = [ 'attachments.*' => 'file|max:10240|mimes:jpg,jpeg,png,gif,webp,mp4,mov,avi,txt,php,js,css,html,json,xml,md,zip', ]; public function mount($conversationId = null, $recipientId = null) { $this->recipientId = $recipientId; if ($conversationId) { $this->selectConversation($conversationId); } elseif ($recipientId) { // Create or find conversation with recipient $conversation = Conversation::findOrCreateDirectConversation(Auth::id(), $recipientId); $this->selectConversation($conversation->id); } else { // Auto-select the most recent conversation $this->loadConversations(); if (!empty($this->conversations) && is_array($this->conversations) && count($this->conversations) > 0) { $firstConversation = reset($this->conversations); if (isset($firstConversation['id'])) { $this->selectConversation($firstConversation['id']); } } } } public function loadConversations() { $service = new MessagingService(); $this->conversations = $service->getUserConversations(Auth::id()) ->get() ->map(function ($conversation) { $isPinned = $conversation->isPinnedForUser(Auth::id()); if ($conversation->type === 'group') { // Group conversation $participants = $conversation->participants()->get(); return [ 'id' => $conversation->id, 'type' => $conversation->type, 'subject' => $conversation->subject, 'participants_count' => $participants->count(), 'participants' => $participants->take(3)->map(function ($user) { $profile = \Paymenter\Extensions\Others\SocialBase\Models\UserProfile::where('user_id', $user->id)->first(); return [ 'id' => $user->id, 'name' => $profile?->display_name ?? $user->name, 'avatar' => $profile?->avatar_url ?? $user->avatar, ]; })->toArray(), 'other_user' => null, 'last_message' => $conversation->lastMessage, 'unread_count' => $conversation->getUnreadCountForUser(Auth::id()), 'last_message_at' => $conversation->last_message_at, 'created_by' => $conversation->created_by, 'is_pinned' => $isPinned, ]; } else { // Direct conversation $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(); } return [ 'id' => $conversation->id, 'type' => $conversation->type, 'subject' => $conversation->subject, 'other_user' => $otherUser ? [ 'id' => $otherUser->id, 'name' => $profile ? $profile->display_name : $otherUser->name, 'avatar' => $profile ? $profile->avatar_url : $otherUser->avatar, ] : null, 'last_message' => $conversation->lastMessage, 'unread_count' => $conversation->getUnreadCountForUser(Auth::id()), 'last_message_at' => $conversation->last_message_at, 'created_by' => $conversation->created_by, 'is_pinned' => $isPinned, ]; } }) ->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 handleGroupCreated($conversationId) { $this->loadConversations(); $this->selectConversation($conversationId); } public function selectConversation($conversationId) { $conversation = Conversation::findOrFail($conversationId); // Check if user is participant if (!$conversation->hasParticipant(Auth::id())) { abort(403); } $this->selectedConversation = $conversationId; $this->loadMessages(); // Mark as read $conversation->markAsReadForUser(Auth::id()); } public function loadMessages() { if (!$this->selectedConversation) { return; } $conversation = Conversation::find($this->selectedConversation); $this->messages = $conversation->messages() ->with(['sender', 'reactions.user']) ->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, 'system_type' => $message->system_type, 'created_at' => $message->created_at, 'is_mine' => $message->sender_id === Auth::id(), 'reactions' => $reactionCounts, 'attachments' => $message->attachments ?? [], ]; }) ->toArray(); // Dispatch event to trigger scroll after messages are loaded $this->dispatch('messagesLoaded'); } public function sendMessage() { if (!$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', ]); $uploadedFiles = []; if (!empty($this->attachments)) { foreach ($this->attachments as $attachment) { // Store in private storage for security $path = $attachment->store('socialbase/attachments', 'local'); $uploadedFiles[] = [ 'path' => $path, 'name' => $attachment->getClientOriginalName(), '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, ]); // Update conversation's last message timestamp $conversation = Conversation::find($this->selectedConversation); if ($conversation) { $conversation->last_message_at = now(); $conversation->save(); } $this->newMessage = ''; $this->attachments = []; $this->loadMessages(); $this->loadConversations(); // Mark as read immediately after sending $conversation->markAsReadForUser(Auth::id()); // Dispatch event for real-time updates $this->dispatch('messageSent'); } public function removeAttachment($index) { unset($this->attachments[$index]); $this->attachments = array_values($this->attachments); } 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->loadConversations(); } public function refreshAll() { $this->loadConversations(); if ($this->selectedConversation) { $this->loadMessages(); // Mark as read when refreshing an open conversation $conversation = Conversation::find($this->selectedConversation); if ($conversation) { $conversation->markAsReadForUser(Auth::id()); } } } public function deleteMessage($messageId) { $message = Message::find($messageId); if (!$message) { $this->dispatch('error', 'Message not found.'); return; } // Only allow user to delete their own messages if ($message->sender_id !== Auth::id()) { $this->dispatch('error', 'You can only delete your own messages.'); return; } try { $message->delete(); $this->loadMessages(); $this->dispatch('success', 'Message deleted successfully.'); } catch (\Exception $e) { $this->dispatch('error', 'Failed to delete message.'); } } 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 } } // Group Management Methods 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 = 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 = 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->loadConversations(); $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->loadConversations(); $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->loadConversations(); } 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 = 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); // Refresh members list $this->openViewMembersModal(); } public function render() { $this->loadConversations(); return view('socialbase::livewire.messaging-interface'); } }