Files
Virtnet.bond/extensions/Others/SocialBase/Livewire/MessagingBubble.php
2025-11-30 11:57:22 +01:00

599 lines
20 KiB
PHP

<?php
namespace Paymenter\Extensions\Others\SocialBase\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Support\Facades\Auth;
use Paymenter\Extensions\Others\SocialBase\Models\Conversation;
use Paymenter\Extensions\Others\SocialBase\Models\Message;
use Paymenter\Extensions\Others\SocialBase\Services\MessagingService;
class MessagingBubble extends Component
{
use WithFileUploads;
public $recentConversations = [];
public $unreadCount = 0;
public $isOpen = false;
public $selectedConversation = null;
public $messages = [];
public $newMessage = '';
public $attachments = [];
public $showMembersModal = false;
public $groupMembers = [];
public $showAddMembersModal = false;
public $availableUsers = [];
public $selectedUsersToAdd = [];
public $showRenameModal = false;
public $newGroupName = '';
protected $listeners = [
'messageReceived' => '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');
}
}