Skip to content

Commit

Permalink
finished chat main functionality with direct updates and websocket in…
Browse files Browse the repository at this point in the history
…itialized updates
  • Loading branch information
JuliusM666 committed Dec 26, 2024
1 parent 1b81931 commit 0acd8d2
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 209 deletions.
26 changes: 16 additions & 10 deletions app/Http/Controllers/MessageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

use App\Models\Message;
use App\Models\User;
use App\Notifications\DeleteMessagesNotification;
use App\Notifications\DeleteConversationNotification;
use App\Notifications\MessageNotification;
use App\Notifications\SeenMessageNotification;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder;

Expand All @@ -30,17 +29,20 @@ private function getConversation($recipient_id)
public function deleteConversation($recipient_id)
{
$this->getConversation($recipient_id)->where("sender_id", auth()->user()->id)->update(["deleted_for_sender" => true]);
$this->getConversation($recipient_id)->where("reciever_id", auth()->user()->id)->update(["deleted_for_reciever" => true]);
auth()->user()->notify(new DeleteMessagesNotification($recipient_id));
$this->getConversation($recipient_id)->where("reciever_id", auth()->user()->id)->update(["deleted_for_reciever" => true, "is_seen" => true]);
auth()->user()->notify(new DeleteConversationNotification($recipient_id));
}

public function markAsSeen(Message $message)
public function markAsSeen($message) # because of soft delete
{
$message = Message::withTrashed()->find($message);
Message::withTrashed()->where("sender_id", $message->sender_id)->where("reciever_id", auth()->user()->id)
->where("created_at", "<=", $message->created_at)
->update(["is_seen" => true]);
auth()->user()->notify(new SeenMessageNotification($message->id));
User::find($message->sender_id)->notify(new SeenMessageNotification($message->id));
$message->refresh();
User::find($message->reciever_id)->notify(new MessageNotification($message->toArray()));
User::find($message->sender_id)->notify(new MessageNotification($message->toArray()));
return $message;
}
/**
* Display a listing of the resource.
Expand Down Expand Up @@ -89,11 +91,13 @@ public function store(Request $request)
'sender_id' => auth()->user()->id,
'reciever_id' => $request->reciever_id,
]);
$message->refresh();
$message->load("sender");
$messageForReciever = $message->toArray();
$messageForReciever['sender'] = $message->reciever()->first();
$messageForSender = $message->toArray();
$messageForSender['sender'] = $message->reciever()->first();
$message->reciever->notify(new MessageNotification($message->toArray(), true));
$message->sender->notify(new MessageNotification($messageForReciever, true));
$message->sender->notify(new MessageNotification($messageForSender, true));
return $messageForSender;
}

/**
Expand Down Expand Up @@ -129,6 +133,7 @@ public function update(Request $request, Message $message)
$message->save();
$message->reciever->notify(new MessageNotification($message->toArray()));
$message->sender->notify(new MessageNotification($message->toArray()));
return $message;
}

/**
Expand All @@ -141,6 +146,7 @@ public function destroy(Message $message)
$message->message = "deleted by user";
$message->reciever->notify(new MessageNotification($message->toArray()));
$message->sender->notify(new MessageNotification($message->toArray()));
return $message;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class DeleteMessagesNotification extends Notification implements ShouldQueue
class DeleteConversationNotification extends Notification implements ShouldQueue
{
use Queueable;

Expand All @@ -32,7 +32,7 @@ public function via(object $notifiable): array

public function broadcastType(): string
{
return 'deleteMessages';
return 'deleteConversation';
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Notifications/MessageNotification.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class MessageNotification extends Notification implements ShouldQueue
*/
private $message;
private $isStore;
private $sender;
public function __construct(array $message, bool $isStore = false)
{
$message['correct_id'] = $message['id'];
$this->message = $message;
$this->isStore = $isStore;
}
Expand Down
13 changes: 11 additions & 2 deletions app/Notifications/PostSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public function toMail(object $notifiable): MailMessage
->line('User ' . $this->reply->user->name . " has replied in this post");
}

public function broadcastType(): string
{
return 'notification';
}

/**
* Get the array representation of the notification.
*
Expand All @@ -56,8 +61,12 @@ public function toMail(object $notifiable): MailMessage
public function toArray(object $notifiable): array
{
return [
'title' => 'post subscription',
'message' => 'User ' . $this->reply->user->name . " has replied in post: " . $this->reply->post->title,
'data' => [
'data' => [
'title' => 'post subscription',
'message' => 'User ' . $this->reply->user->name . " has replied in post: " . $this->reply->post->title
]
]
];
}

Expand Down
51 changes: 0 additions & 51 deletions app/Notifications/SeenMessageNotification.php

This file was deleted.

13 changes: 11 additions & 2 deletions app/Notifications/ThemeSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public function toMail(object $notifiable): MailMessage
->action('Show Post', route('post', [$this->post->theme->topic, $this->post->theme, $this->post]));
}

public function broadcastType(): string
{
return 'notification';
}
/**
* Get the array representation of the notification.
*
Expand All @@ -56,8 +60,13 @@ public function toMail(object $notifiable): MailMessage
public function toArray(object $notifiable): array
{
return [
'title' => 'theme subscription',
'message' => 'User ' . $this->post->user->name . " has posted in theme: " . $this->post->theme->title,
'data' => [
'data' => [
'title' => 'theme subscription',
'message' => 'User ' . $this->post->user->name . " has posted in theme: " . $this->post->theme->title
]

]
];
}

Expand Down
1 change: 1 addition & 0 deletions resources/js/Components/Chat/chat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function Chat({ setActiveChat, chats, setChats, nextPageUrl }) {
</li>
)
})}
{chats.length == 0 && <h1 className="text-end p-2">No chats yet</h1>}
{loading && <div className="flex justify-center"><Loading /></div>}
</ul>
)
Expand Down
119 changes: 103 additions & 16 deletions resources/js/Components/Chat/chats.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,115 @@
import { useContext } from "react"
import { useContext, useState, useRef, useEffect } from "react"
import { usePage, router } from "@inertiajs/react"
import { ModalContext } from "../Context/modalContext"
import CloseButton from "../closeButton"
import Card from "../card"
import Chat from "./chat"
import Messages from "./messages"

export default function Chats({ close, chats, setChats, nextPageUrl }) {
export default function Chats({ close, showChats }) {
const { activeChat, setActiveChat } = useContext(ModalContext) // recipient id
const { auth } = usePage().props
const [messages, setMessages] = useState([])
const [chats, setChats] = useState([])
const chatsRef = useRef(chats)
const messagesRef = useRef(messages)
const activeChatRef = useRef(activeChat)
const nextPageUrl = useRef(route("chats.index"))
useEffect(() => { messagesRef.current = messages }, [messages])
useEffect(() => { chatsRef.current = chats }, [chats])
useEffect(() => { activeChatRef.current = activeChat }, [activeChat])
function updateChats(message) {
if (chatsRef.current.length == 0 && nextPageUrl.current != null) {
return
}
let messageExists = false
for (let i = 0; i < chatsRef.current.length; i++) {
if ((chatsRef.current[i].reciever_id == message.reciever_id && chatsRef.current[i].sender_id == message.sender_id) || (chatsRef.current[i].sender_id == message.reciever_id && chatsRef.current[i].reciever_id == message.sender_id)) {
messageExists = true
if (chatsRef.current[i].created_at <= message.created_at) {
message.sender = chatsRef.current[i].sender
const newChats = [...chatsRef.current]
if (message.is_edited == false && message.deleted_at == null) {
newChats.splice(i, 1)
newChats.unshift(message)
}
else {
newChats[i] = message
}
setChats(newChats)
}
break
}
}
if (messageExists == false && message.is_edited == false && message.deleted_at == null) {
setChats([message, ...chatsRef.current])
}

}
function updateMessages(message) {
if ((activeChatRef.current == message.reciever_id && auth.user.id == message.sender_id) || (activeChatRef.current == message.sender_id && auth.user.id == message.reciever_id)) {
let messageId = null
for (let i = 0; i < messagesRef.current.length; i++) {
if (messagesRef.current[i].id == message.id) {
message.sender = messagesRef.current[i].sender
messageId = i
break
}

}

if (messageId == null && message.is_edited == false && message.deleted_at == null) {
setMessages([message, ...messagesRef.current])
}
else if (messageId != null) {
const newMessages = [...messagesRef.current]
newMessages[messageId] = message
setMessages(newMessages)
}
}
}
function update(message) {
if (message.correct_id != null) {
message.id = message.correct_id
}
updateChats(message)
updateMessages(message)
}
function deleteConversation(recipient_id) {
setChats((chats) => chats.filter(chat => chat.sender.id !== recipient_id))
}
useEffect(() => {
if (auth.user) {
window.Echo.private('App.Models.User.' + auth.user.id)
.notification((notification) => {
router.reload({ only: ['chatsNotSeen'] })
console.log(notification)
if (notification.type == "message") {
update(notification)
}
else if (notification.type == "deleteConversation") {
deleteConversation(notification.recipient_id)
}
})
return () => {
window.Echo.leave('App.Models.User.' + auth.user.id)
}
}
}, [])
return (
<div className="fixed z-20 text-slate-700 right-0 bottom-0 w-1/4 max-xl:w-1/3 max-lg:w-1/2 max-md:w-8/12 max-sm:w-11/12">
<Card name="Chats" ButtonComponent={<CloseButton handleOnClick={() => close()} />}>
<div className="bg-slate-100">
{activeChat == null && <Chat nextPageUrl={nextPageUrl} chats={chats} setChats={setChats} setActiveChat={setActiveChat} />}
{activeChat != null && <Messages setSeen={((id) => {
setChats(chats.map((chat) => {
if (chat.id == id) {
chat.is_seen = true
}
return chat
}))
})} removeChat={(id) => { setChats(chats.filter(chat => chat.sender.id !== id)) }} activeChat={activeChat} setActiveChat={setActiveChat} />}
<>
{showChats &&
<div className="fixed z-20 text-slate-700 right-0 bottom-0 w-1/4 max-xl:w-1/3 max-lg:w-1/2 max-md:w-8/12 max-sm:w-11/12">
<Card name="Chats" ButtonComponent={<CloseButton handleOnClick={() => close()} />}>
<div className="bg-slate-100">
{activeChat == null && <Chat nextPageUrl={nextPageUrl} chats={chats} setChats={setChats} setActiveChat={setActiveChat} />}
{activeChat != null && <Messages deleteConversation={deleteConversation} messagesRef={messagesRef} update={update} messages={messages} setMessages={setMessages} activeChat={activeChat} setActiveChat={setActiveChat} />}
</div>
</Card>
</div>
</Card>
</div>
}

</>
)

}
16 changes: 11 additions & 5 deletions resources/js/Components/Chat/message.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useContext } from "react"
import { ModalContext } from "../Context/modalContext"
import { usePage, router } from "@inertiajs/react"
import { usePage } from "@inertiajs/react"
import UserPicture from "../userPicture"

export default function Message({ message, setActiveMessage, isActive, setInput, setShowEmoji, setIsEdit, isEdit }) {
export default function Message({ message, isSeen, setActiveMessage, isActive, setInput, setShowEmoji, setIsEdit, isEdit, update }) {
const { setShowConfirm, confirmAction, confirmMessage } = useContext(ModalContext)
const { auth } = usePage().props
if (message.sender.id == auth.user.id) {
function deleteMessage() {
axios.delete(route("chats.destroy", message.id))
.then((response) => { update(response.data) })
.catch((error) => console.log(error))
}
if (message.sender_id == auth.user.id) {
return (
<div className="my-2">
{isActive &&
Expand All @@ -17,7 +22,7 @@ export default function Message({ message, setActiveMessage, isActive, setInput,
<button onClick={() => { isEdit ? setIsEdit(false) : (setInput(message.message), setIsEdit(true)) }} className="hover:opacity-70"> <i className="fa-regular fa-pen-to-square" /></button>
<button onClick={() => {
confirmMessage.current = "This message will be deleted. Do you want to confirm?", setShowConfirm(true),
confirmAction.current = () => router.delete(route("chats.destroy", message.id))
confirmAction.current = () => deleteMessage()
}} className="hover:opacity-70"> <i className="fa-solid fa-circle-xmark" /></button>
</>
}
Expand All @@ -37,11 +42,12 @@ export default function Message({ message, setActiveMessage, isActive, setInput,
</button>

<div className="h-9 w-9">
<UserPicture user_id={message.sender.id} user_img={message.sender.user_img} />
<UserPicture user_id={auth.user.id} user_img={auth.user.user_img} />
</div>

</div>
<div className="text-end text-xs px-1 mr-16">
{isSeen && <h1>read</h1>}
{message.is_edited == true && <h1>edited</h1>}
</div>
</div >
Expand Down
Loading

0 comments on commit 0acd8d2

Please sign in to comment.