refactor: chat time, grouping and toast
- show message time - group messages by date - show toast when message sent is failed
This commit is contained in:
parent
44b13a5ebf
commit
b2132f8c02
@ -3,7 +3,9 @@
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Events\MessageSent;
|
||||
use App\Exceptions\MessageNotSendException;
|
||||
use App\Models\User;
|
||||
use DB;
|
||||
|
||||
final readonly class SendMessageAction
|
||||
{
|
||||
@ -14,18 +16,30 @@ public function __construct(private CreateOrGetInboxAction $inboxAction) {}
|
||||
*/
|
||||
public function execute(User $sender, User $recipient, array $data): void
|
||||
{
|
||||
try {
|
||||
|
||||
// find the inbox between the two users
|
||||
\DB::beginTransaction();
|
||||
DB::beginTransaction();
|
||||
$inbox = $this->inboxAction->execute($recipient, $sender);
|
||||
|
||||
// update the inbox with the last message and last user as current user
|
||||
$inbox->last_message = $data['message'];
|
||||
$inbox->last_user_id = $sender->id;
|
||||
$inbox->save();
|
||||
|
||||
// create a new message in the inbox
|
||||
$message = $inbox->messages()->create([
|
||||
'message' => $data['message'],
|
||||
'user_id' => $sender->id,
|
||||
]);
|
||||
|
||||
// Send the message to all other users in the inbox
|
||||
broadcast(new MessageSent($message))->toOthers();
|
||||
\DB::commit();
|
||||
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw new MessageNotSendException('Message not sent.', previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
app/Exceptions/MessageNotSendException.php
Normal file
7
app/Exceptions/MessageNotSendException.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class MessageNotSendException extends Exception {}
|
||||
@ -4,9 +4,12 @@
|
||||
|
||||
use App\Actions\CreateOrGetInboxAction;
|
||||
use App\Actions\SendMessageAction;
|
||||
use App\Exceptions\MessageNotSendException;
|
||||
use App\Http\Requests\SendMessageRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Container\Attributes\CurrentUser;
|
||||
use Log;
|
||||
use Throwable;
|
||||
|
||||
class ChatController extends Controller
|
||||
{
|
||||
@ -21,8 +24,8 @@ public function show(#[CurrentUser] User $sender, User $recipient, CreateOrGetIn
|
||||
{
|
||||
try {
|
||||
$inbox = $action->execute($recipient, $sender);
|
||||
} catch (\Throwable $e) {
|
||||
\Log::error('Inbox instantiation Failed: ', [$e->getMessage()]);
|
||||
} catch (Throwable $e) {
|
||||
Log::error('Inbox instantiation Failed: ', [$e->getMessage()]);
|
||||
abort(500);
|
||||
}
|
||||
|
||||
@ -33,7 +36,7 @@ public function show(#[CurrentUser] User $sender, User $recipient, CreateOrGetIn
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Throwable
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function store(
|
||||
#[CurrentUser] User $sender,
|
||||
@ -41,8 +44,16 @@ public function store(
|
||||
SendMessageRequest $request,
|
||||
SendMessageAction $action
|
||||
) {
|
||||
try {
|
||||
$action->execute($sender, $recipient, $request->validated());
|
||||
|
||||
response()->json(['message' => 'Message sent successfully.']);
|
||||
return response()->json(['message' => 'Message sent successfully.']);
|
||||
|
||||
} catch (MessageNotSendException $e) {
|
||||
|
||||
Log::error('Message send failed', [$e->getMessage()]);
|
||||
|
||||
return response()->json(['message' => 'Message sent failed.'], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Eloquent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@ -14,24 +17,24 @@
|
||||
* @property string|null $delivered_at
|
||||
* @property string|null $deleted_at
|
||||
* @property string|null $failed_at
|
||||
* @property \Illuminate\Support\Carbon $created_at
|
||||
* @property-read \App\Models\Inbox|null $inbox
|
||||
* @property-read \App\Models\User|null $user
|
||||
* @property Carbon $created_at
|
||||
* @property-read Inbox|null $inbox
|
||||
* @property-read User|null $user
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereDeliveredAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereFailedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereInboxId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereMessage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereReadAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Message whereUserId($value)
|
||||
* @method static Builder<static>|Message newModelQuery()
|
||||
* @method static Builder<static>|Message newQuery()
|
||||
* @method static Builder<static>|Message query()
|
||||
* @method static Builder<static>|Message whereCreatedAt($value)
|
||||
* @method static Builder<static>|Message whereDeletedAt($value)
|
||||
* @method static Builder<static>|Message whereDeliveredAt($value)
|
||||
* @method static Builder<static>|Message whereFailedAt($value)
|
||||
* @method static Builder<static>|Message whereId($value)
|
||||
* @method static Builder<static>|Message whereInboxId($value)
|
||||
* @method static Builder<static>|Message whereMessage($value)
|
||||
* @method static Builder<static>|Message whereReadAt($value)
|
||||
* @method static Builder<static>|Message whereUserId($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
class Message extends Model
|
||||
{
|
||||
@ -43,6 +46,10 @@ class Message extends Model
|
||||
'created_at', 'delivered_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function inbox(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Inbox::class);
|
||||
|
||||
@ -1,34 +1,51 @@
|
||||
import {showToast} from "./toast.js";
|
||||
|
||||
export const sendMessage = async (element, event) => {
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
const messageInput = element.querySelector('[name="message"]');
|
||||
if (!messageInput.value) return;
|
||||
const message = messageInput.value;
|
||||
if (!message) return;
|
||||
|
||||
const recipentId = element.dataset.recipientId;
|
||||
const message = messageInput.value;
|
||||
messageInput.value = '';
|
||||
|
||||
addMessageToChat({message: message}, true);
|
||||
// Capture the ID of the UI element we just added
|
||||
const tempMessageId = addMessageToChat({message: message}, true);
|
||||
|
||||
const response = await axios.post(`/api/chat/${recipentId}/message`, {message: message});
|
||||
try {
|
||||
await axios.post(`/api/chat/${recipentId}/message`, {message: message});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Message could not be sent.');
|
||||
|
||||
const failedMessage = document.getElementById(tempMessageId);
|
||||
if (failedMessage) {
|
||||
failedMessage.remove();
|
||||
}
|
||||
|
||||
messageInput.value = message;
|
||||
}
|
||||
}
|
||||
|
||||
export const addMessageToChat = (message, right = false) => {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
if (!chatContainer) return;
|
||||
|
||||
const tempId = 'msg-' + Date.now();
|
||||
|
||||
const placeholder = chatContainer.querySelector('#no-messages-placeholder');
|
||||
if (placeholder) {
|
||||
placeholder.remove();
|
||||
}
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
const messagePlaceholder = `
|
||||
<div class="grid px-4 my-1 w-full ${right ? 'place-items-end' : 'place-items-start'}">
|
||||
<div class="max-w-[40vw] py-2 px-4 rounded-2xl bg-gray-200">
|
||||
<div id="${tempId}" class="grid px-4 my-1 w-full ${right ? 'place-items-end' : 'place-items-start'}">
|
||||
<div class="max-w-[40vw] py-2 px-4 rounded-full ${right ? 'rounded-br-none' : 'rounded-tl-none'} bg-gray-200">
|
||||
${message.message}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
chatContainer.insertAdjacentHTML('afterbegin', messagePlaceholder);
|
||||
|
||||
chatContainer.scrollTop = 0;
|
||||
|
||||
// Return the ID so the caller can find this specific message later
|
||||
return tempId;
|
||||
}
|
||||
|
||||
@ -1,14 +1,32 @@
|
||||
@props(['recipient', 'messages' => []])
|
||||
@php
|
||||
$groupedMessages = $messages->groupBy(function($msg) {
|
||||
return $msg->created_at->format('M j, Y');
|
||||
});
|
||||
@endphp
|
||||
|
||||
<x-dashboard.page-heading class="m-0 mb-0.5" :title="$recipient->name" description="offline"/>
|
||||
<div class="bg-gray-50 h-full overflow-hidden flex-shrink-0">
|
||||
<div id="chat-container" data-auth-id="{{ auth()->id() }}" data-partner-id="{{ $recipient->id }}"
|
||||
class="text-sm flex flex-col-reverse overflow-y-scroll h-full max-h-screen pb-50 scroll-snap-y-container">
|
||||
@forelse($messages as $message)
|
||||
<x-chat.message :right="$message->user_id === auth()->user()->id">{{$message->message}}</x-chat.message>
|
||||
@empty
|
||||
<div id="no-messages-placeholder" class="grid px-4 my-1 w-full h-full place-items-center ">
|
||||
<p class="text-gray-600">No Messages Found!</p>
|
||||
@forelse($groupedMessages as $date => $dayMessages)
|
||||
@foreach($dayMessages as $message)
|
||||
<x-chat.message :right="$message->user_id === auth()->id()">
|
||||
<p>{{ $message->message }}</p>
|
||||
<span class="text-[10px] opacity-70 block text-right mt-1">
|
||||
{{ $message->created_at->format('g:i A') }}
|
||||
</span>
|
||||
</x-chat.message>
|
||||
@endforeach
|
||||
|
||||
{{-- The Date Divider --}}
|
||||
<div class="flex justify-center my-4 top-0">
|
||||
<span class="bg-gray-200 text-gray-600 text-xs px-3 py-1 rounded-full shadow-sm">
|
||||
{{ $date }}
|
||||
</span>
|
||||
</div>
|
||||
@empty
|
||||
<div id="no-messages-placeholder">No Messages Found !</div>
|
||||
@endforelse
|
||||
</div>
|
||||
<x-chat.message-input :recipient_id="$recipient->id"/>
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
@props(['right' => false])
|
||||
<div class="grid px-4 my-1 w-full @if($right) place-items-end @else place-items-start @endif">
|
||||
<div class="max-w-[40vw] py-2 px-4 @if($right) rounded-l-xl rounded-br-xl @else rounded-r-xl rounded-bl-xl @endif bg-gray-200">
|
||||
{{ $slot }}
|
||||
|
||||
<div class="max-w-[70vw] md:max-w-[40vw] py-2 px-4 bg-gray-200 rounded-3xl
|
||||
@if($right)
|
||||
rounded-br-none
|
||||
@else
|
||||
rounded-tl-none
|
||||
@endif">
|
||||
{{$slot}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
@php use App\Enums\UserTypes; @endphp
|
||||
@props(['profileLink' => ''])
|
||||
<div class="flex items-center">
|
||||
@auth
|
||||
@ -13,7 +14,7 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@if(auth()->check() && auth()->user()->role === \App\Enums\UserTypes::Broker->value)
|
||||
@if(auth()->check() && auth()->user()->role === UserTypes::Broker->value)
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{route('broker.dashboard')}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
@ -22,6 +23,8 @@
|
||||
<p>Control Panel</p>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{route('chat')}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
@ -30,8 +33,6 @@
|
||||
<p>Chat</p>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
@php use App\Services\ProfileInitialsService; @endphp
|
||||
<x-chat.layout>
|
||||
<x-slot:sidebarItems>
|
||||
@forelse($inboxes as $inbox)
|
||||
<x-chat.sidebar-item
|
||||
:name="$inbox->recipient->name"
|
||||
:link="route('chat.show', $inbox->recipient->id)"
|
||||
:avatar="(new \App\Services\ProfileInitialsService)->create($inbox->recipient->name)"
|
||||
:avatar="(new ProfileInitialsService)->create($inbox->recipient->name)"
|
||||
:message="$inbox->last_message"/>
|
||||
@empty
|
||||
No chats found !
|
||||
@ -20,4 +21,5 @@
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<x-ui.toast/>
|
||||
</x-chat.layout>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user