Add all files
This commit is contained in:
613
extensions/Others/SocialBase/Livewire/MessagingInterface.php
Normal file
613
extensions/Others/SocialBase/Livewire/MessagingInterface.php
Normal file
@@ -0,0 +1,613 @@
|
||||
<?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;
|
||||
use App\Models\User;
|
||||
|
||||
class MessagingInterface extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
public $conversations;
|
||||
public $selectedConversation = null;
|
||||
public $messages = [];
|
||||
public $newMessage = '';
|
||||
public $recipientId = null;
|
||||
public $attachments = [];
|
||||
|
||||
public $showMembersModal = false;
|
||||
public $showAddMembersModal = false;
|
||||
public $showRenameModal = false;
|
||||
public $groupMembers = [];
|
||||
public $availableUsers = [];
|
||||
public $selectedUsersToAdd = [];
|
||||
public $newGroupName = '';
|
||||
|
||||
protected $listeners = [
|
||||
'messageReceived' => '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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user