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 authored and kuv2707 committed Jun 22, 2024
1 parent e222290 commit 3a8332e
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 7 deletions.
9 changes: 9 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,13 @@ 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;

export type Message = {
content: string;
ref?: string | null;
atMentions?: string[];
reactions?: [string, string][];
playerName: string;
};

//todo: Add more events
39 changes: 34 additions & 5 deletions frontend/package-lock.json

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

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

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

useEffect(() => {
const handleReact = (event: CustomEvent) => {

Check failure on line 12 in frontend/src/library/chatbox/Chatbox.tsx

View workflow job for this annotation

GitHub Actions / eslint-frontend

'handleReact' is assigned a value but never used
const { content, emoji } = event.detail;
setMessages((prevMessages) =>
prevMessages.map((message) =>
message.content === content
? {
...message,
reactions: [
...message.reactions!,
[emoji, 'Player 1'],
],
}
: message
)
);
};
}, []);

return (
<div className="fixed bottom-5 right-5 z-50 flex flex-col items-end">
<div
className={`w-80 h-96 bg-lime-400 rounded-lg overflow-hidden mb-2 transition-all duration-300 ${
isVisible
? 'opacity-100 shadow-2xl'
: 'opacity-0 pointer-events-none shadow-none'
}`}
style={{
boxShadow: isVisible
? '0px 20px 30px rgba(0, 0, 0, 0.2)'
: 'none',
}}
>
<div className="flex flex-col h-full">
<div className="flex-grow overflow-y-auto">
<MessageList messages={messages} />
</div>
<div className="border-t border-gray-300">
<MessageInput setMessages={setMessages} />
</div>
</div>
</div>
<button
className="p-3 bg-lime-500 text-gray-700 rounded-full focus:outline-none transform transition-transform duration-300 hover:scale-105 hover:bg-lime-400 active:bg-lime-600 shadow-md"
onClick={() => setIsVisible(!isVisible)}
>
<FaComments className="w-7 h-7" />
</button>
</div>
);
};

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

interface MessageProps {
message: MessageType;
}

const Message: React.FC<MessageProps> = ({ message }) => {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);

const handleEmojiClick = (emojiData: EmojiClickData) => {
const event = new CustomEvent('MESSAGE_REACT', {
detail: { content: message.content, emoji: emojiData.emoji },
});
window.dispatchEvent(event);
setShowEmojiPicker(false);
};

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>
<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">
{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-4 h-4" />
</button>
{showEmojiPicker && (
<div className="bg-white border border-gray-300 -left-6 absolute z-50">
<EmojiPicker onEmojiClick={handleEmojiClick} />
</div>
)}
</div>
</div>
);
};

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

interface MessageInputProps {
setMessages: React.Dispatch<React.SetStateAction<MessageType[]>>;
}

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

const handleSend = async () => {
if (content.trim() !== '') {
const newMessage: MessageType = {

Check failure on line 16 in frontend/src/library/chatbox/MessageInput.tsx

View workflow job for this annotation

GitHub Actions / eslint-frontend

'newMessage' is assigned a value but never used
content,
ref: null,
atMentions: [],
reactions: [],
playerName: 'Player 1',
};

// Simulate an API call to send the message
setContent('');
}
};

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

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.ctrlKey && e.key === 'Enter') {
handleSend();
}
};

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)}
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>
);
};

// Simulate an API call to send the message

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

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

export default MessageList;
3 changes: 3 additions & 0 deletions frontend/src/pages/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Button from '../library/button';
import { useModal } from '../library/modal/ModalContext';
import { useToast } from '../library/toast/toast-context';
import CopyButton from '../library/copyButton';
import Chatbox from '../library/chatbox/Chatbox';

interface GameProps {
currentGame: string;
Expand Down Expand Up @@ -177,6 +178,8 @@ const Game: React.FC<GameProps> = ({ currentGame }) => {
</div>
</div>
</div>

<Chatbox />
</div>
);
};
Expand Down

0 comments on commit 3a8332e

Please sign in to comment.