pending messages are displayed on the gray

This commit is contained in:
slawk0
2024-11-19 22:49:55 +01:00
parent f4c68ecbc2
commit 23b6f6793d
10 changed files with 163 additions and 88 deletions

View File

@@ -13,6 +13,7 @@
"@types/socket.io-client": "^1.4.36",
"axios": "^1.7.7",
"js-cookie": "^3.0.5",
"nanoid": "^5.0.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
@@ -889,9 +890,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
"integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -2045,9 +2046,9 @@
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -3249,9 +3250,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz",
"integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==",
"funding": [
{
"type": "github",
@@ -3260,10 +3261,10 @@
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
"node": "^18 || >=20"
}
},
"node_modules/natural-compare": {
@@ -3624,6 +3625,24 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@@ -15,6 +15,7 @@
"@types/socket.io-client": "^1.4.36",
"axios": "^1.7.7",
"js-cookie": "^3.0.5",
"nanoid": "^5.0.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",

View File

@@ -1,16 +1,10 @@
import { axiosClient } from '../App.tsx';
import { ChatMessages } from '../pages/Chat.tsx';
type ContactsProps = {
usernamecontact: string;
read: boolean;
};
type MessagesProps = {
sender: string;
message: string;
recipient: string;
message_id: number;
timestamp: string;
};
export async function getContactsList(): Promise<ContactsProps[]> {
try {
const response = await axiosClient.get(`/api/chat/contacts`);
@@ -51,7 +45,7 @@ export async function getMessages(
contact: string | null,
cursor: number | null = 0,
limit: number = 50,
): Promise<{ messages: MessagesProps[] }> {
): Promise<{ messages: ChatMessages[] }> {
if (contact === null || cursor === null) {
return { messages: [] };
}

View File

@@ -1,17 +1,10 @@
import { useEffect } from 'react';
import { deleteContact, getContactsList } from '../../api/contactsApi.tsx';
import { ChatMessages } from '../../pages/Chat.tsx';
type ContactsProps = {
usernamecontact: string;
read: boolean;
};
type MessagesProps = {
sender: string;
message: string;
recipient: string;
message_id: number;
timestamp: string;
};
type ContactsListProps = {
InitializeContact: (contact: string) => void;
@@ -19,7 +12,7 @@ type ContactsListProps = {
contactsList: ContactsProps[];
setCurrentContact: React.Dispatch<React.SetStateAction<string | null>>;
updateContactStatus: (contactObj: ContactsProps, read: true) => void;
setMessages: React.Dispatch<React.SetStateAction<MessagesProps[]>>;
setMessages: React.Dispatch<React.SetStateAction<ChatMessages[]>>;
currentContact: string | null;
};

View File

@@ -1,24 +1,26 @@
import { useRef, useEffect, useCallback } from 'react';
import { useRef, useCallback, useEffect } from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import type { KeyboardEventHandler } from 'react';
import { sendMessage } from '../../socket/socket.tsx';
import { socket } from '../../socket/socket.tsx';
import { customAlphabet } from 'nanoid';
import { useOutletContext } from 'react-router-dom';
import { ChatMessages } from '../../pages/Chat.tsx';
const nanoid = customAlphabet('1234567890', 5);
type Input = {
message: string;
};
type MessageFormProps = {
contact: string;
setMessages: React.Dispatch<React.SetStateAction<ChatMessages[]>>;
messages: ChatMessages[];
};
const MessageForm = ({ contact }: MessageFormProps) => {
const {
register,
handleSubmit,
reset,
watch,
formState: {},
} = useForm<Input>({ mode: 'onChange' });
const MessageForm = ({ contact, setMessages }: MessageFormProps) => {
const { username }: { username: string } = useOutletContext();
const { register, handleSubmit, reset, watch } = useForm<Input>({
mode: 'onChange',
});
const message = watch('message', '');
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
@@ -37,20 +39,64 @@ const MessageForm = ({ contact }: MessageFormProps) => {
adjustHeight();
}, [message, adjustHeight]);
if (!socket) {
console.error('Socket not initialized');
return;
}
// Sending message
const submitMessage: SubmitHandler<Input> = (data) => {
if (!data.message) {
if (!data.message) return;
if (!socket) {
console.error('Socket not initialized');
return;
}
// for (let i = 0; i <= 200; i++) {
// sendMessage(i, contact);
// }
sendMessage(data.message.trim(), contact);
reset({ message: '' });
const tempId: string = nanoid(); // Temporary ID for unsent message
const tempMessage: ChatMessages = {
sender: username,
message: data.message.trim(),
recipient: contact,
message_id: 0, // Set to 0 because of key={msg.message_id || msg.tempId} in messages list
pending: true,
tempId: tempId,
};
setMessages((prevMessages) => [...prevMessages, tempMessage]); // Display as gray
socket.emit(
'chat message',
{
message: data.message.trim(),
recipient: contact,
tempId: tempId,
},
(response: { status: string; message_id: number; tempId: string }) => {
if (response.status === 'ok') {
setMessages((prevMessages) =>
prevMessages.map((msg) =>
msg.tempId === tempId
? { ...msg, pending: false, message_id: response.message_id }
: msg,
),
);
reset({ message: '' });
}
console.log(response.status, response.tempId);
},
);
console.log('sent message: ', {
message: data.message.trim(),
recipient: contact,
tempId: tempId,
});
};
// Handle Enter and Ctrl+Enter
// Handle Enter and Ctrl+Enter in textarea
const handleKeyPress: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();

View File

@@ -1,17 +1,9 @@
import { useEffect, useRef, useState } from 'react';
import { socket } from '../../socket/socket.tsx';
import { useOutletContext } from 'react-router-dom';
import { UsernameType } from '../../utils/ProtectedRoutes.tsx';
import { sendContact } from '../../api/contactsApi.tsx';
import LoadingWheel from './LoadingWheel.tsx';
type ChatMessages = {
sender: string;
message: string;
recipient: string;
message_id: number;
timestamp: string;
};
import { ChatMessages } from '../../pages/Chat.tsx';
type ContactProps = {
usernamecontact: string;
@@ -45,7 +37,7 @@ function MessagesArea({
}: MessagesAreaProps) {
const messagesEndRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const { username }: UsernameType = useOutletContext();
const { username }: { username: string } = useOutletContext();
const prevScrollHeight = useRef(0);
const [isFetchingHistory, setIsFetchingHistory] = useState(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
@@ -92,7 +84,7 @@ function MessagesArea({
);
console.log('changed status to false for: ', msg.sender);
}
if (msg.sender == currentContact || msg.sender == username) {
if (msg.sender == currentContact) {
messageHandler(msg);
}
});
@@ -107,7 +99,6 @@ function MessagesArea({
currentContainer.removeEventListener('scroll', handleScroll);
}
socket.off('chat message');
socket.off('historical');
};
}, [currentContact, username, setContactsList, updateContactStatus]);
@@ -127,10 +118,14 @@ function MessagesArea({
const messageList = messages.map((msg: ChatMessages) => (
<li
className="whitespace-pre-wrap ml-2 rounded p-1 hover:bg-gray-800"
key={msg.message_id}
className={`whitespace-pre-wrap ml-2 rounded p-1 ${
msg.pending
? 'text-gray-400' // Gray style for pending messages
: 'hover:bg-gray-800'
}`}
key={msg.message_id || msg.tempId}
>
{msg.message_id} {msg.sender}: {msg.message}
{msg.tempId} {msg.message_id} {msg.sender}: {msg.message}
</li>
));

View File

@@ -9,12 +9,13 @@ import { initializeSocket } from '../socket/socket.tsx';
import Cookies from 'js-cookie';
import { getMessages, setContactStatus } from '../api/contactsApi.tsx';
type ChatMessages = {
export type ChatMessages = {
sender: string;
message: string;
recipient: string;
message_id: number;
timestamp: string;
tempId: string;
pending: boolean;
};
type ContactsProps = {
usernamecontact: string;
@@ -162,7 +163,11 @@ function Chat() {
</div>
<div className="flex-shrink-0 mb-2 mt-0">
{currentContact && currentContact.length >= 4 ? (
<MessageForm contact={currentContact} />
<MessageForm
contact={currentContact}
setMessages={setMessages}
messages={messages}
/>
) : null}
</div>
</div>

View File

@@ -1,7 +1,6 @@
import io from 'socket.io-client';
import Socket = SocketIOClient.Socket;
let socket: Socket | null = null;
function initializeSocket(token: string): Socket | null {
if (!socket && token) {
socket = io({
@@ -21,20 +20,27 @@ function initializeSocket(token: string): Socket | null {
return socket;
}
function sendMessage(message: string, recipient: string) {
function sendMessage(message: string, recipient: string, tempId: string) {
if (!socket) {
console.error('Socket not initialized');
return;
}
socket.emit('chat message', {
message: message,
recipient: recipient,
});
socket.emit(
'chat message',
{
message: message,
recipient: recipient,
tempId: tempId,
},
(response: { status: string; tempId: string }) => {
console.log(response.status, response.tempId);
},
);
console.log('sent message: ', {
message: message,
recipient: recipient,
tempId: tempId,
});
}

View File

@@ -253,6 +253,9 @@ app.get("/api/chat/messages/:contact", authorizeUser, async (req, res) => {
});
app.post("/api/chat/sendmessage", authorizeUser, async (req, res) => {
const message = req.body.message?.trim();
//TODO filter for invalid characters in message
return res.status(500).json({ message: "HUJ!" });
});

View File

@@ -62,30 +62,43 @@ function initializeSocket(io) {
}
socket.join(username); // join username room
socket.on("chat message", async (msg) => {
socket.on("chat message", async (msg, callback) => {
const { message, recipient } = msg;
const sender = username;
if (!message || !recipient) {
callback({ status: "error", message: "Invalid message or recipient" });
return;
}
const insertedMessage = await insertMessage(username, recipient, message);
const { message_id, timestamp } = insertedMessage;
console.log("(socket) received from chat message", msg);
try {
const insertedMessage = await insertMessage(sender, recipient, message);
if (!insertedMessage) {
callback({ status: "error", message: "Failed to insert message" });
return;
}
io.to(username).to(recipient).emit("chat message", {
sender,
message,
recipient,
message_id,
});
console.log("(socket) sent on 'chat message' socket: ", {
sender,
message,
recipient,
timestamp,
message_id,
});
const { message_id, timestamp } = insertedMessage;
console.log("(socket) received from chat message", msg);
io.to(username).to(recipient).emit("chat message", {
sender,
message,
recipient,
message_id,
});
console.log("(socket) sent on 'chat message' socket: ", {
sender,
message,
recipient,
timestamp,
message_id,
});
callback({ status: "ok", tempId: msg.tempId });
} catch (e) {
console.error("(socket) Failed to insert message ", e);
callback({ status: "error", message: "Internal server error" });
}
});
socket.on("disconnect", (reason) => {