feat: use randomized user in chat

This commit is contained in:
Ante Brähler
2024-11-23 00:48:55 +00:00
parent ee768c4f9a
commit 0137b89238
10 changed files with 82 additions and 37 deletions

View File

@@ -1,9 +1,19 @@
class User {
constructor(name, avatar) {
this.name = name;
this.avatar = avatar;
}
}
class ChatMessage {
constructor(userId, message, timestamp) {
this.userId = userId;
static nextId = 0;
constructor(user, message, timestamp) {
this.id = ChatMessage.nextId++;
this.user = user;
this.message = message;
this.timestamp = timestamp;
}
}
module.exports = ChatMessage;
module.exports = { ChatMessage, User };

View File

@@ -1,4 +1,4 @@
const ChatMessage = require('../models/ChatMessage');
const { ChatMessage, User } = require('../models/ChatMessage');
// At the moment, this service is not used! It is only a placeholder for future development for a persistent chat.
class ChatService {
@@ -6,8 +6,8 @@ class ChatService {
this.messages = [];
}
addMessage(userId, message, timestamp) {
const newChatMessage = new ChatMessage(userId, message, timestamp);
addMessage(userName, userAvatar, message, timestamp) {
const newChatMessage = new ChatMessage(new User(userName, userAvatar), message, timestamp);
this.messages.push(newChatMessage);
return newChatMessage;
}

View File

@@ -2,9 +2,9 @@ const ChatService = require('../services/ChatService');
const ChatMessage = require('../models/ChatMessage');
module.exports = (io, socket) => {
socket.on('send-message', ({ userId, message, timestamp }) => {
socket.on('send-message', ({ userName, userAvatar, message, timestamp }) => {
const chatMessage = ChatService.addMessage(userId, message, timestamp);
const chatMessage = ChatService.addMessage(userName, userAvatar, message, timestamp);
socket.broadcast.emit('chat-message', chatMessage) // Broadcast to all clients except sender
});
};

View File

@@ -29,8 +29,6 @@ function App() {
.catch((error) => console.error('Error loading current channel:', error));
socketService.connect();
console.log('Subscribing to events');
const channelAddedListener = (channel: Channel) => {
setChannels((prevChannels) => [...prevChannels, channel]);
@@ -43,6 +41,8 @@ function App() {
socketService.subscribeToEvent('channel-added', channelAddedListener);
socketService.subscribeToEvent('channel-selected', channelSelectedListener);
socketService.connect();
return () => {
socketService.unsubscribeFromEvent('channel-added', channelAddedListener);
socketService.unsubscribeFromEvent('channel-selected', channelSelectedListener);

View File

@@ -1,29 +1,50 @@
import React, { useState, useEffect } from 'react';
import { Send, MessageSquare } from 'lucide-react';
import socketService from '../../services/SocketService';
import { Channel, ChatMessage } from '../../types';
import { Channel, ChatMessage, RandomUser, User } from '../../types';
import { SendMessage } from './SendMessage';
import { SystemMessage } from './SystemMessage';
import { ReceivedMessage } from './ReceivedMessage';
import apiService from '../../services/ApiService';
function Chat() {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [newMessage, setNewMessage] = useState('');
const [userName] = useState('You');
const [user, setUser] = useState<User>();
useEffect(() => {
apiService
.request<RandomUser>('/api', 'GET', 'https://randomuser.me')
.then((randomUser) => {
const name = randomUser.results[0].name;
const picture = randomUser.results[0].picture;
setUser({
name: `${name.first} ${name.last}`,
avatar: picture.medium,
});
})
.catch((error) => console.error('Error fetching random user:', error));
const messageListener = (message: ChatMessage) => {
setMessages((prevMessages) => [...prevMessages, message]);
};
socketService.subscribeToEvent('chat-message', messageListener);
const channelSelectedListener = (selectedChannel: Channel) => {
const systemMessage = {
message: `Switched to ${selectedChannel.name}'s stream`,
timestamp: new Date().toISOString(),
userId: 'System',
};
setMessages((prevMessages) => [...prevMessages, systemMessage]);
setMessages((prev) => [
...prev,
{
id: prev.length ? prev[prev.length -1].id + 1 : 1,
user: {
name: 'System',
avatar: '',
},
message: `Switched to ${selectedChannel.name}'s stream`,
timestamp: new Date().toISOString(),
userId: 'System',
}
]);
}
socketService.subscribeToEvent('channel-selected', channelSelectedListener);
@@ -35,14 +56,15 @@ function Chat() {
const handleSendMessage = (e: React.FormEvent) => {
e.preventDefault();
if (!newMessage.trim()) return;
if (!newMessage.trim() || !user) return;
socketService.sendMessage(userName, newMessage, new Date().toISOString());
socketService.sendMessage(user.name, user.avatar, newMessage, new Date().toISOString());
setMessages((prev) => [
...prev,
{
userId: userName,
id: prev.length ? prev[prev.length -1].id + 1 : 1,
user: user,
message: newMessage,
timestamp: new Date().toISOString(),
},
@@ -59,12 +81,12 @@ function Chat() {
<div className="h-[calc(100vh-13rem)] overflow-y-auto p-4 space-y-4 scroll-container vertical-scroll-container">
{messages.map((msg) => {
if(msg.userId === userName) {
return <SendMessage msg={msg}></SendMessage>;
} else if(msg.userId === 'System') {
return <SystemMessage msg={msg}></SystemMessage>;
if(msg.user.name === user?.name) {
return <SendMessage key={msg.id} msg={msg}></SendMessage>;
} else if(msg.user.name === 'System') {
return <SystemMessage key={msg.id} msg={msg}></SystemMessage>;
} else {
return <ReceivedMessage msg={msg}></ReceivedMessage>;
return <ReceivedMessage key={msg.id} msg={msg}></ReceivedMessage>;
}
})}
</div>

View File

@@ -5,11 +5,10 @@ export function ReceivedMessage({ msg }: {
}) {
return (
<div className="flex items-start space-x-3 pr-10">
{/* TODO: fetch random images */}
<img src={`https://images.unsplash.com/photo-${Math.floor(Math.random() * 1000)}?w=64&h=64&fit=crop&crop=faces`} alt={msg.userId} className="w-8 h-8 rounded-full" />
<img src={msg.user.avatar} alt={msg.user.name} className="w-8 h-8 rounded-full" />
<div>
<div className="flex items-center space-x-2">
<span className="font-medium">{msg.userId}</span>
<span className="font-medium">{msg.user.name}</span>
<span className="text-xs text-gray-400">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>

View File

@@ -5,11 +5,10 @@ export function SendMessage({ msg }: {
}) {
return (
<div className="flex items-start justify-end space-x-3 pl-10">
{/* TODO: fetch random images */}
<img src={`https://images.unsplash.com/photo-${Math.floor(Math.random() * 1000)}?w=64&h=64&fit=crop&crop=faces`} alt={msg.userId} className="w-8 h-8 rounded-full" />
<img src={msg.user.avatar} alt={msg.user.name} className="w-8 h-8 rounded-full" />
<div>
<div className="flex items-center space-x-2">
<span className="font-medium">{msg.userId}</span>
<span className="font-medium">{msg.user.name}</span>
<span className="text-xs text-gray-400">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>

View File

@@ -10,7 +10,7 @@ const apiService = {
* @param body - The request body (e.g. POST)
* @returns Ein Promise with the parsed JSON response to class T
*/
async request<T>(path: string, method: HttpMethod = 'GET', body?: unknown): Promise<T> {
async request<T>(path: string, method: HttpMethod = 'GET', api_url: string = API_BASE_URL, body?: unknown): Promise<T> {
try {
const options: RequestInit = {
method,
@@ -23,7 +23,7 @@ const apiService = {
options.body = JSON.stringify(body);
}
const response = await fetch(`${API_BASE_URL}${path}`, options);
const response = await fetch(`${api_url}${path}`, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);

View File

@@ -61,10 +61,10 @@ class SocketService {
// Nachricht senden
sendMessage(userId: string, message: string, timestamp: string) {
sendMessage(userName: string, userAvatar: string, message: string, timestamp: string) {
if (!this.socket) throw new Error('Socket is not connected.');
this.socket.emit('send-message', { userId, message, timestamp });
this.socket.emit('send-message', { userName, userAvatar, message, timestamp });
}
// Channel hinzufügen

View File

@@ -4,6 +4,20 @@ export interface User {
avatar: string;
}
export interface RandomUser {
results: {
name: {
first: string;
last: string;
},
picture: {
large: string;
medium: string;
thumbnail: string;
}
}[];
};
export interface Channel {
id: number;
name: string;
@@ -13,7 +27,8 @@ export interface Channel {
}
export interface ChatMessage {
userId: string;
id: number;
user: User;
message: string;
timestamp: string;
}