Skip to content

Commit

Permalink
frontend: Added a chat box in the game page
Browse files Browse the repository at this point in the history
Added a collapsible chat box in the gamepage,
the necessary files are added in a folder Chatbox
in src/library

fixes #125
  • Loading branch information
sksmagr23 committed Jun 20, 2024
1 parent e3a9bdf commit 1993d75
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 1 deletion.
29 changes: 29 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
},
"dependencies": {
"autoprefixer": "^10.4.19",
"emoji-picker-react": "^4.10.0",
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
"react-router-dom": "^6.23.1",
"tailwindcss": "^3.4.3"
},
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/library/chatbox/Chatbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useState } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import { Message as MessageType } from './types';

const Chatbox: React.FC = () => {
const [messages, setMessages] = useState<MessageType[]>([]);

const handleSendMessage = (content: string) => {
const newMessage: MessageType = {
content,
ref: null,
atMentions: [],
reactions: [],
playerName: 'Player 1'
};
setMessages([...messages, newMessage]);
};

const handleReactToMessage = (content: string, emoji: string) => {
setMessages((prevMessages) =>
prevMessages.map((message) =>
message.content === content
? { ...message, reactions: [...message.reactions!, [emoji, 'Player 1']] }
: message
)
);
};

return (
<div className="flex flex-col h-full bg-lime-400 shadow-lg rounded-lg">
<div className="flex-grow overflow-y-auto">
<MessageList messages={messages} onReact={handleReactToMessage} />
</div>
<div className="border-t border-gray-300">
<MessageInput onSendMessage={handleSendMessage} />
</div>
</div>
);
};

export default Chatbox;
47 changes: 47 additions & 0 deletions frontend/src/library/chatbox/Message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, {useState} from 'react';
import EmojiPicker, {EmojiClickData} from 'emoji-picker-react';
import { Message as MessageType } from './types';
import { FaRegSmile } from 'react-icons/fa';

interface MessageProps {
message: MessageType;
onReact: (content: string, emoji: string) => void;
}

const Message: React.FC<MessageProps> = ({ message, onReact }) => {

const [showEmojiPicker, setShowEmojiPicker] = useState(false);

const handleEmojiClick = (emojiData: EmojiClickData) => {
onReact(message.content, emojiData.emoji);
setShowEmojiPicker(false);
};

return (
<div className="relative bg-gray-200 text-gray-800 p-3 mb-4 max-w-xs rounded-xl">
<div className="absolute -top-2 left-2 text-blue-800 font-kavoon text-xs px-2 py-0 rounded-t-xl">
{message.playerName}
</div>
<div className="break-words font-kavoon">{message.content}</div>
<div className="absolute bottom-0 left-0 transform translate-x-1/4 translate-y-1/2 w-4 h-4 bg-gray-200 rotate-45"></div>
<div className="mt-2 flex space-x-2 items-center relative">
{message.reactions?.map(([emoji], index) => (
<span key={index} className="text-sm">{emoji}</span>
))}
<button
className="text-gray-500 hover:text-gray-700"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
>
<FaRegSmile className="w-5 h-5" />
</button>
{showEmojiPicker && (
<div className="bg-white border border-gray-300 -left-6 absolute z-50">
<EmojiPicker onEmojiClick={handleEmojiClick} />
</div>
)}
</div>
</div>
);
};

export default Message;
60 changes: 60 additions & 0 deletions frontend/src/library/chatbox/MessageInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// library/Chatbox/MessageInput.tsx
import React, { useState } from 'react';
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react';
import Button from '../button';

interface MessageInputProps {
onSendMessage: (content: string) => void;
}

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

const handleSend = () => {
if (content.trim() !== '') {
onSendMessage(content);
setContent('');
}
};

const onEmojiClick = (emojiData: EmojiClickData) => {
setContent(content + emojiData.emoji);
setShowEmojiPicker(false);
};

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>
)}
<input
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
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>
);
};

export default MessageInput;
20 changes: 20 additions & 0 deletions frontend/src/library/chatbox/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import Message from './Message';
import { Message as MessageType } from './types';

interface MessageListProps {
messages: MessageType[];
onReact: (content: string, emoji: string) => void;
}

const MessageList: React.FC<MessageListProps> = ({ messages, onReact }) => {
return (
<div className="flex-l p-2">
{messages.map((message, index) => (
<Message key={index} message={message} onReact={onReact} />
))}
</div>
);
};

export default MessageList;
7 changes: 7 additions & 0 deletions frontend/src/library/chatbox/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type Message = {
content: string;
ref?: string | null;
atMentions?: string[];
reactions?: [string, string][];
playerName: string;
}
19 changes: 18 additions & 1 deletion frontend/src/pages/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';
import React, { useState } from 'react';
import { useGameContext } from '../contexts/GameContext';
import Button from '../library/button';
import Chatbox from '../library/chatbox/Chatbox';
import { FaComments } from 'react-icons/fa';

const Game: React.FC = () => {
const { gameState } = useGameContext();
const [isChatboxOpen, setIsChatboxOpen] = useState(false);

if (!gameState) {
return (
Expand Down Expand Up @@ -104,6 +107,20 @@ const Game: React.FC = () => {
</div>
</div>
</div>
{/* Chatbox Toggle Button */}
<button
className="absolute bottom-6 right-6 p-3 bg-lime-500 text-gray-700 rounded-full focus:outline-none"
onClick={() => setIsChatboxOpen(!isChatboxOpen)}
>
<FaComments className="w-8 h-7" />
</button>

{/* Chatbox */}
{isChatboxOpen && (
<div className="absolute bottom-16 right-6 w-80 h-96 bg-lime-400 shadow-lg rounded-lg overflow-hidden">
<Chatbox />
</div>
)}
</div>
);
};
Expand Down

0 comments on commit 1993d75

Please sign in to comment.