Skip to content

Commit

Permalink
Game:Add support for replying to messages in Message box
Browse files Browse the repository at this point in the history
This commit enables replying to others in chat during game
and rendering the message being replied to in the chatbox.

fixes #153
  • Loading branch information
ritwik-69 authored and kuv2707 committed Jun 26, 2024
1 parent 72ddf1c commit 051484e
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 47 deletions.
15 changes: 8 additions & 7 deletions backend/src/controllers/gameControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import {

export async function handleGameJoin(req: AuthRequest, res: Response) {
const gameCode = req.body.code;
const activeGameId = req.user.activeGameId;
if (activeGameId) {
res.status(400).send({
error: 'User is already playing a game',
});
return;
}
//todo: uncomment this when we have the system to end games
// const activeGameId = req.user.activeGameId;
// if (activeGameId) {
// res.status(400).send({
// error: 'User is already playing a game',
// });
// return;
// }
const game = retrieveGame(gameCode);
if (!game) {
res.status(404).send({ error: 'Game not found' });
Expand Down
1 change: 0 additions & 1 deletion backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ export type AppEventType = GameEventTypes | ChatEventTypes;
// Represent all the events that can be sent to the client
// a workaround for now to make things work - this will be refactored later
export type AppEvent = GameEvent | ChatEvent;

export type ChatMessage = {
content: string;
id: string;
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/library/chatbox/Chatbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useToast } from '../toast/toast-context';
const Chatbox: React.FC = () => {
const [messages, setMessages] = useState<{ [k: string]: ChatMessage }>({});
const [isVisible, setIsVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState<ChatMessage | null>(null);
const toast = useToast();

useEffect(() => {
Expand Down Expand Up @@ -78,10 +79,16 @@ const Chatbox: React.FC = () => {
>
<div className="flex flex-col h-full">
<div className="flex-grow overflow-y-auto">
<MessageList messages={Object.values(messages)} />
<MessageList
messages={messages}
setReplyMessage={setReplyMessage}
/>
</div>
<div className="border-t border-gray-300">
<MessageInput />
<MessageInput
replyMessage={replyMessage}
setReplyMessage={setReplyMessage}
/>
</div>
</div>
</div>
Expand Down
33 changes: 30 additions & 3 deletions frontend/src/library/chatbox/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import EmojiPicker, { EmojiClickData } from 'emoji-picker-react';
import { ChatEventTypes, ChatMessage } from '../../../../backend/src/types';
import { FaRegSmile } from 'react-icons/fa';
import { triggerEvent } from '../../channel';

interface MessageProps {
message: ChatMessage;
id: string;
messages: { [k: string]: ChatMessage };
replyCallback?: (message: ChatMessage) => void;
}

const Message: React.FC<MessageProps> = ({ message }) => {
const Message: React.FC<MessageProps> = ({ id, messages, replyCallback }) => {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const message = messages[id];
const replyingTo = messages[message.ref || ''];

const handleEmojiClick = (emojiData: EmojiClickData) => {
triggerEvent({
Expand All @@ -21,12 +24,30 @@ const Message: React.FC<MessageProps> = ({ message }) => {
});
setShowEmojiPicker(false);
};
const handleReply = () => {
replyCallback?.(message);
};

return (
<div className="relative bg-gray-200 text-gray-800 p-2.5 mb-6 max-w-xs rounded-xl">
<div className="absolute -top-2 left-0 text-blue-900 font-kavoon text-xs px-2 py-0 rounded-t-2xl bg-gray-200">
{message.playerName}
</div>

{replyingTo && (
<div className="flex-1 p-2 bg-gray-300 rounded-t-lg border border-gray-500 ">
<p className="text-sm text-gray-900 font-kavoon">
{replyingTo?.playerName}
</p>

<p className="text-sm text-gray-600 font-kavoon">
{replyingTo.content.length > 35
? replyingTo.content.slice(0, 35) + '...'
: replyingTo.content}
</p>
</div>
)}

<div className="break-words font-kavoon">{message.content}</div>
<div className="absolute bottom-0 left-3 transform translate-x-1/4 translate-y-1/2 w-4 h-4 bg-gray-200 rotate-45"></div>
<div className="mt-1 flex space-x-1 items-center relative">
Expand All @@ -46,6 +67,12 @@ const Message: React.FC<MessageProps> = ({ message }) => {
<EmojiPicker onEmojiClick={handleEmojiClick} />
</div>
)}
<button
className="text-gray-500 hover:text-gray-700"
onClick={handleReply}
>
Reply
</button>
</div>
</div>
);
Expand Down
98 changes: 68 additions & 30 deletions frontend/src/library/chatbox/MessageInput.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
import React, { useState } from 'react';
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react';
import Button from '../button';
import { ChatEventTypes } from '../../../../backend/src/types';
import { ChatEventTypes, ChatMessage } from '../../../../backend/src/types';
import { triggerEvent } from '../../channel';
import { useAuth } from '../../contexts/AuthContext';

const MessageInput: React.FC = () => {
interface MessageInputProps {
replyMessage: ChatMessage | null;
setReplyMessage: React.Dispatch<React.SetStateAction<ChatMessage | null>>;
}

const MessageInput: React.FC<MessageInputProps> = ({
replyMessage,
setReplyMessage,
}) => {
const [content, setContent] = useState('');
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const auth = useAuth();

const handleSend = async () => {
handleClose();
console.log(replyMessage?.id);
if (content.trim() !== '') {
triggerEvent({
type: ChatEventTypes.SEND_MESSAGE,
data: {
id: Date.now().toString(),
content: content.trim(),
playerName: auth.getUser()?.name || 'Unknown user',
ref: replyMessage?.id,
},
});
setContent('');
setReplyMessage(null);
}
};

Expand All @@ -34,38 +46,64 @@ const MessageInput: React.FC = () => {
handleSend();
}
};
const handleClose = () => {
setReplyMessage(null);
};

return (
<div className="relative flex p-1 border-t border-gray-300">
<button
className="mr-1 p-2 bg-white text-gray-800 rounded-xl border-2 border-gray-500"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
>
😊
</button>
{showEmojiPicker && (
<div className="absolute bottom-14 left-0 z-50">
<EmojiPicker onEmojiClick={onEmojiClick} />
<div className="relative p-1 border-t border-gray-300">
{replyMessage && (
<div className="flex items-center mb-2">
<div className="flex-1 p-2 bg-gray-200 rounded-t-lg border-b border-gray-500 ">
<p className="text-sm text-gray-900 font-kavoon">
Replying to: {replyMessage.playerName}
</p>

<p className="text-sm text-gray-600 font-kavoon">
{replyMessage && replyMessage.content.length > 35
? replyMessage.content.slice(0, 35) + '...'
: replyMessage.content}
</p>
<button
className="absolute right-0 top-0 px-2 py-0 bg-none cursor-pointer border-transparent text-black"
onClick={handleClose}
>
{'x'}
</button>
</div>
</div>
)}
<input
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleKeyDown}
className="flex-0.5 px-1 py-1 border-2 border-gray-500 rounded-xl font-kavoon text-sm"
placeholder="Type a message..."
/>
<Button
variant="accept"
size="medium"
backgroundColor="bg-gray-400"
buttonSize="w-34 h-11"
className="ml-2 border-2"
onClick={handleSend}
>
Send
</Button>
<div className="flex items-center">
<button
className="mr-1 p-2 bg-white text-gray-800 rounded-xl border-2 border-gray-500"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
>
😊
</button>
{showEmojiPicker && (
<div className="absolute bottom-14 left-0 z-50">
<EmojiPicker onEmojiClick={onEmojiClick} />
</div>
)}
<input
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleKeyDown}
className="flex-1 px-1 py-1 border-2 border-gray-500 rounded-xl font-kavoon text-sm"
placeholder="Type a message..."
/>
<Button
variant="accept"
size="medium"
backgroundColor="bg-gray-400"
buttonSize="w-34 h-11"
className="ml-2 border-2"
onClick={handleSend}
>
Send
</Button>
</div>
</div>
);
};
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/library/chatbox/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import React from 'react';
import Message from './Message';
import { ChatMessage } from '../../../../backend/src/types';
interface MessageListProps {
messages: ChatMessage[];
messages: { [k: string]: ChatMessage };
setReplyMessage: React.Dispatch<React.SetStateAction<ChatMessage | null>>;
}

const MessageList: React.FC<MessageListProps> = ({ messages }) => {
const MessageList: React.FC<MessageListProps> = ({
messages,
setReplyMessage,
}) => {
return (
<div className="flex-l p-2">
{messages.map((message, index) => (
<Message key={index} message={message} />
{Object.values(messages).map((message, index) => (
<Message
key={index}
id={message.id}
messages={messages}
replyCallback={(k) => setReplyMessage(k)}
/>
))}
</div>
);
Expand Down

0 comments on commit 051484e

Please sign in to comment.