diff --git a/app/Actions/CreateOrGetInboxAction.php b/app/Actions/CreateOrGetInboxAction.php index c4bb121..25a199e 100644 --- a/app/Actions/CreateOrGetInboxAction.php +++ b/app/Actions/CreateOrGetInboxAction.php @@ -9,13 +9,12 @@ final readonly class CreateOrGetInboxAction { /** - * * @throws \Throwable */ public function execute(User $recipient, User $sender): Inbox { - $existingInbox = Inbox::whereHas('users', fn($q) => $q->where('id', $sender->id)) - ->whereHas('users', fn($q) => $q->where('id', $recipient->id)) + $existingInbox = Inbox::whereHas('users', fn ($q) => $q->where('users.id', $sender->id)) + ->whereHas('users', fn ($q) => $q->where('users.id', $recipient->id)) ->first(); if ($existingInbox) { diff --git a/app/Actions/SendMessageAction.php b/app/Actions/SendMessageAction.php new file mode 100644 index 0000000..1b7c292 --- /dev/null +++ b/app/Actions/SendMessageAction.php @@ -0,0 +1,27 @@ +inboxAction->execute($recipient, $sender); + $message = $inbox->messages()->create([ + 'message' => $data['message'], + 'user_id' => $sender->id, + ]); + + broadcast(new MessageSent($message))->toOthers(); + + } +} diff --git a/app/Events/MessageSent.php b/app/Events/MessageSent.php new file mode 100644 index 0000000..741caa0 --- /dev/null +++ b/app/Events/MessageSent.php @@ -0,0 +1,38 @@ + + */ + public function broadcastOn(): array + { + $users = $this->message->inbox->users->pluck('id')->toArray(); + sort($users); + + return [ + new PrivateChannel("chat.{$users[0]}.{$users[1]}"), + ]; + } +} diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index 21ead6a..4546d39 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -2,21 +2,45 @@ namespace App\Http\Controllers; +use App\Actions\CreateOrGetInboxAction; +use App\Actions\SendMessageAction; +use App\Http\Requests\SendMessageRequest; use App\Models\User; use Illuminate\Container\Attributes\CurrentUser; -use Illuminate\Http\Request; class ChatController extends Controller { // public function index(#[CurrentUser] User $user) { - return view('dashboards.user.chat') ; + return view('dashboards.user.chat'); } - public function show(User $recipient) + public function show(#[CurrentUser] User $sender, User $recipient, CreateOrGetInboxAction $action) { - return view('dashboards.user.chat') - ->with('recipient', $recipient); + try { + $inbox = $action->execute($recipient, $sender); + } catch (\Throwable $e) { + \Log::error('Inbox instantiation Failed: ', [$e->getMessage()]); + abort(500); + } + + return view('dashboards.user.chat') + ->with('recipient', $recipient) + ->with('chats', $inbox->messages()->latest()->get()); + } + + /** + * @throws \Throwable + */ + public function store( + #[CurrentUser] User $sender, + User $recipient, + SendMessageRequest $request, + SendMessageAction $action + ) { + $action->execute($sender, $recipient, $request->validated()); + + response()->json(['message' => 'Message sent successfully.']); } } diff --git a/app/Http/Requests/SendMessageRequest.php b/app/Http/Requests/SendMessageRequest.php new file mode 100644 index 0000000..22318f6 --- /dev/null +++ b/app/Http/Requests/SendMessageRequest.php @@ -0,0 +1,20 @@ + 'required|string', + ]; + } + + public function authorize(): bool + { + return true; + } +} diff --git a/app/Models/Inbox.php b/app/Models/Inbox.php index 0a6a3c1..f0bf79f 100644 --- a/app/Models/Inbox.php +++ b/app/Models/Inbox.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; /** @@ -15,6 +16,7 @@ * @property-read \App\Models\User|null $lastUser * @property-read \Illuminate\Database\Eloquent\Collection $users * @property-read int|null $users_count + * * @method static \Illuminate\Database\Eloquent\Builder|Inbox newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Inbox newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Inbox query() @@ -23,11 +25,13 @@ * @method static \Illuminate\Database\Eloquent\Builder|Inbox whereLastMessage($value) * @method static \Illuminate\Database\Eloquent\Builder|Inbox whereLastUserId($value) * @method static \Illuminate\Database\Eloquent\Builder|Inbox whereUpdatedAt($value) + * * @mixin \Eloquent */ class Inbox extends Model { protected $fillable = ['last_user_id', 'last_message']; + public function users(): BelongsToMany { return $this->belongsToMany(User::class); @@ -37,4 +41,9 @@ public function lastUser(): HasOne { return $this->hasOne(User::class, 'id', 'last_user_id'); } + + public function messages(): HasMany + { + return $this->hasMany(Message::class, 'inbox_id'); + } } diff --git a/resources/js/app.js b/resources/js/app.js index fa0e154..1df6d57 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -15,6 +15,7 @@ import {deleteRecentSearch} from "./deleteRecentSearch.js"; import {initNavMenu} from "./nav-menu.js"; import {toggleShimmer} from "./shimmer.js"; import {follow} from "./interaction.js"; +import {addMessageToChat, sendMessage} from "./message.js"; document.deleteSearch = deleteRecentSearch; document.like = like; @@ -23,7 +24,8 @@ document.redirect = redirect; document.showReportModal = showReportModal; window.toggleShimmer = toggleShimmer; window.follow = follow; - +window.sendMessage = sendMessage; +window.addMessageToChat = addMessageToChat; window.addEventListener('load', async () => { const preloader = document.getElementById('preloader'); const content = document.getElementById('content'); diff --git a/resources/js/message.js b/resources/js/message.js new file mode 100644 index 0000000..91d1d1d --- /dev/null +++ b/resources/js/message.js @@ -0,0 +1,34 @@ +export const sendMessage = async (element, event) => { + event.preventDefault() + const messageInput = element.querySelector('[name="message"]'); + if (!messageInput.value) return; + + const recipentId = element.dataset.recipientId; + const message = messageInput.value; + messageInput.value = ''; + + addMessageToChat({message: message}, true); + + const response = await axios.post(`/api/chat/${recipentId}/message`, {message: message}); +} + +export const addMessageToChat = (message, right = false) => { + const chatContainer = document.getElementById('chat-container'); + if (!chatContainer) return; + + const placeholder = chatContainer.querySelector('#no-messages-placeholder'); + if (placeholder) { + placeholder.remove(); + } + + const messagePlaceholder = ` +
+
+ ${message.message} +
+
`; + + chatContainer.insertAdjacentHTML('afterbegin', messagePlaceholder); + + chatContainer.scrollTop = 0; +} diff --git a/resources/views/components/chat/message-box.blade.php b/resources/views/components/chat/message-box.blade.php index a37d70d..b961a49 100644 --- a/resources/views/components/chat/message-box.blade.php +++ b/resources/views/components/chat/message-box.blade.php @@ -1,14 +1,41 @@ @props(['recipient', 'chats' => []])
-
+
@forelse($chats as $chat) - {{$chat->text}} + @ds($chat) + {{$chat->message}} @empty -
+

No Messages Found!

@endforelse
- +
+ + diff --git a/resources/views/components/chat/message-input.blade.php b/resources/views/components/chat/message-input.blade.php index 96a12c7..a9a9d23 100644 --- a/resources/views/components/chat/message-input.blade.php +++ b/resources/views/components/chat/message-input.blade.php @@ -1,8 +1,10 @@ +@props(['recipient_id'])
-
- hi + + - +
diff --git a/resources/views/dashboards/user/chat.blade.php b/resources/views/dashboards/user/chat.blade.php index 11295a2..7d77b5b 100644 --- a/resources/views/dashboards/user/chat.blade.php +++ b/resources/views/dashboards/user/chat.blade.php @@ -6,7 +6,7 @@ @if(isset($recipient)) - + @else

Start a chat !

diff --git a/routes/api/api.php b/routes/api/api.php index 8d7dbf8..54718af 100644 --- a/routes/api/api.php +++ b/routes/api/api.php @@ -1,5 +1,6 @@ name('recent-search.destroy'); + + Route::post('/chat/{recipient}/message', [ChatController::class, 'store'])->name('chat.message'); }); diff --git a/routes/channels.php b/routes/channels.php index df2ad28..1bcf7c1 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -1,7 +1,13 @@ id === (int) $id; }); + +Broadcast::channel('chat.{user1}.{user2}', function (User $user, int $user1, int $user2) { + // Only allow the user if their ID matches one of the two in the channel name + return $user->id === $user1 || $user->id === $user2; +});