Overview
The Chat component is a Livewire component that handles displaying messages, sending new messages, and managing conversation interactions. It supports private, group, and self conversations with real-time updates via Laravel Echo.
Component Location
Wirechat\Wirechat\Livewire\Chat\Chat
Usage
Blade
<livewire:wirechat.chat :conversation="$conversation" />
Livewire
$this->dispatch('openChatWidget', conversation: $conversationId);
Properties
The conversation instance being displayed
The current message input text
Collection of loaded messages grouped by date
Array of media attachments being uploaded
Array of file attachments being uploaded
The message being replied to, if any
Whether more messages can be loaded
Public Methods
sendMessage()
Sends a new message in the conversation.
public function sendMessage(): void
Validation:
- Body is required if no attachments
- Files must match configured MIME types and size limits
- Rate limited to 60 messages per minute
Example:
<form wire:submit="sendMessage">
<input type="text" wire:model="body" />
<button type="submit">Send</button>
</form>
sendLike()
Sends a heart emoji (❤️) as a quick message.
public function sendLike(): void
Example:
<button wire:click="sendLike">
<span>❤️</span>
</button>
setReply()
Sets a message to reply to.
public function setReply(string $id): void
Example:
<button wire:click="setReply('{{ encrypt($message->id) }}')'">
Reply
</button>
removeReply()
Removes the current reply context.
public function removeReply(): void
Example:
@if($replyMessage)
<div>
Replying to: {{ $replyMessage->body }}
<button wire:click="removeReply">Cancel</button>
</div>
@endif
Deletes a message for the current user only.
public function deleteForMe(string $id): void
Example:
<button wire:click="deleteForMe('{{ encrypt($message->id) }}')'">
Delete for me
</button>
The message remains visible to other participants.
deleteForEveryone()
Deletes a message for all participants.
public function deleteForEveryone(string $id): void
Authorization:
- User must own the message OR be an admin in group conversations
Example:
@if($message->ownedBy(auth()->user()) || auth()->user()->isAdminIn($conversation))
<button wire:click="deleteForEveryone('{{ encrypt($message->id) }}')'">
Delete for everyone
</button>
@endif
If the message has replies, it will be soft deleted. Otherwise, it’s permanently deleted.
loadMore()
Loads older messages in the conversation.
public function loadMore(): void
Example:
@if($canLoadMore)
<button wire:click="loadMore">Load more messages</button>
@endif
deleteConversation()
Deletes the conversation for the current user.
public function deleteConversation(): void
Example:
<button wire:click="deleteConversation"
wire:confirm="Are you sure you want to delete this chat?">
Delete Chat
</button>
clearConversation()
Clears all messages in the conversation for the current user.
public function clearConversation(): void
Example:
<button wire:click="clearConversation"
wire:confirm="Are you sure you want to clear this chat?">
Clear Chat
</button>
exitConversation()
Exits a group conversation.
public function exitConversation(): void
Restrictions:
- Only available for group conversations
- Group owners cannot exit
Example:
@if($conversation->isGroup() && !auth()->user()->isOwnerOf($conversation))
<button wire:click="exitConversation"
wire:confirm="Are you sure you want to exit this group?">
Exit Group
</button>
@endif
Real-time Events
The component listens for these broadcast events:
MessageCreated
Triggered when a new message is sent by another user.
Channel: {panelId}.conversation.{conversationId}
Handler: appendNewMessage()
public function appendNewMessage($event): void
MessageDeleted
Triggered when a message is deleted by another user.
Channel: {panelId}.conversation.{conversationId}
Handler: removeDeletedMessage()
public function removeDeletedMessage($event): void
Dispatched Events
Scrolls the chat to the bottom.
Livewire.on('scroll-bottom', () => {
// Scroll implementation
});
Focuses the message input field.
Livewire.on('focus-input-field', () => {
document.querySelector('input[wire:model="body"]').focus();
});
refresh
Refreshes the chat list component.
$this->dispatch('refresh')->to(Chats::class);
close-chat
Closes the chat (widget mode).
$this->dispatch('close-chat');
File Uploads
The component uses Livewire’s WithFileUploads trait.
Supported Upload Types
Media: Images, videos (configurable MIME types)
Files: Documents, archives (configurable MIME types)
Configuration
$panel
->mediaMaxUploadSize(10240) // 10MB
->fileMaxUploadSize(20480) // 20MB
->maxUploads(5) // Max 5 files per message
->mediaMimes(['image/jpeg', 'image/png', 'image/gif'])
->fileMimes(['application/pdf', 'application/zip']);
Upload Example
<input type="file"
wire:model="media"
accept="image/*"
multiple>
<input type="file"
wire:model="files"
accept=".pdf,.zip"
multiple>
@error('media.*') <span>{{ $message }}</span> @enderror
@error('files.*') <span>{{ $message }}</span> @enderror
Rate Limiting
The component includes rate limiting to prevent spam:
- Limit: 60 messages per minute per user
- Action: Returns 429 error when exceeded
protected function rateLimit(): void
{
$perMinute = 60;
if (RateLimiter::tooManyAttempts('send-message:'.auth()->id(), $perMinute)) {
abort(429, __('wirechat::chat.messages.rate_limit'));
}
RateLimiter::increment('send-message:'.auth()->id());
}
Message Grouping
Messages are automatically grouped by date:
- Today: “Today”
- Yesterday: “Yesterday”
- Last 7 days: Day name (e.g., “Monday”)
- Older: Date format (e.g., “15/01/2026”)
private function messageGroupKey(Message $message): string
{
$messageDate = $message->created_at;
if ($messageDate->isToday()) {
return __('wirechat::chat.message_groups.today');
} elseif ($messageDate->isYesterday()) {
return __('wirechat::chat.message_groups.yesterday');
} elseif ($messageDate->greaterThanOrEqualTo(now()->subDays(7))) {
return $messageDate->format('l');
}
return $messageDate->format('d/m/Y');
}
Complete Example
<div class="chat-container">
<!-- Header -->
<div class="chat-header">
<h3>{{ $conversation->name ?? $receiver->wirechat_name }}</h3>
@if($conversation->isGroup() && !auth()->user()->isOwnerOf($conversation))
<button wire:click="exitConversation">Exit Group</button>
@endif
<button wire:click="deleteConversation">Delete</button>
</div>
<!-- Messages -->
<div class="messages">
@if($canLoadMore)
<button wire:click="loadMore">Load More</button>
@endif
@foreach($loadedMessages as $date => $messages)
<div class="message-group">
<div class="date-header">{{ $date }}</div>
@foreach($messages as $message)
<div class="message">
{{ $message->body }}
<button wire:click="setReply('{{ encrypt($message->id) }}')'">
Reply
</button>
<button wire:click="deleteForMe('{{ encrypt($message->id) }}')'">
Delete for me
</button>
@if($message->ownedBy(auth()->user()))
<button wire:click="deleteForEveryone('{{ encrypt($message->id) }}')'">
Delete for everyone
</button>
@endif
</div>
@endforeach
</div>
@endforeach
</div>
<!-- Reply Preview -->
@if($replyMessage)
<div class="reply-preview">
Replying to: {{ $replyMessage->body }}
<button wire:click="removeReply">×</button>
</div>
@endif
<!-- Input -->
<form wire:submit="sendMessage">
<input type="file" wire:model="media" multiple accept="image/*">
<input type="file" wire:model="files" multiple>
<input type="text" wire:model="body" placeholder="Type a message...">
<button type="submit">Send</button>
<button type="button" wire:click="sendLike">❤️</button>
</form>
</div>