Open Mkurowski03 opened 1 month ago
To implement a peer-to-peer chat feature, we need to create a backend service to store and retrieve chat messages, update the frontend to interact with this backend, and manage the chat state effectively. The solution involves updating endpoints, removing mocked data, configuring Apollo Client, managing state with Jotai, and creating a backend service.
Update the requests/endpoints.ts
file to include new endpoints for the chat service.
requests/endpoints.ts
export const httpEndpoint = 'https://bigshortbets.squids.live/bigsb-testnet/graphql';
export const wsEndpoint = 'wss://bigshortbets.squids.live/bigsb-testnet/graphql';
export const chatHttpEndpoint = 'https://bigshortbets.squids.live/bigsb-testnet/chat';
export const chatWsEndpoint = 'wss://bigshortbets.squids.live/bigsb-testnet/chat';
Remove the mocked data from components/Market/TradingHub/Chat/mockedData.ts
.
components/Market/TradingHub/Chat/mockedData.ts
export {};
Ensure Apollo Client is configured to use the new chat service endpoints in requests/graphql.ts
.
requests/graphql.ts
import { ApolloClient, HttpLink, InMemoryCache, split } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { httpEndpoint, wsEndpoint } from './endpoints';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({
uri: httpEndpoint,
});
const wsLink = new GraphQLWsLink(
createClient({
url: wsEndpoint,
})
);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
export const apolloClient = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
connectToDevTools: true,
});
Add state management for chat messages in store/store.ts
.
store/store.ts
import { atom } from 'jotai';
import { TradingHubStateType } from '@/components/Market/TradingHub/TradingHub';
import { EnrichedMarketType } from '@/types/marketTypes';
export const marketsAtom = atom<EnrichedMarketType[]>([]);
export const chosenMarketAtom = atom<EnrichedMarketType | undefined>(undefined);
export const tradingHubStateAtom = atom<TradingHubStateType>('chart');
export const tradingHubPositionsCountAtom = atom<number>(0);
export const tradingHubOrdersCountAtom = atom<number>(0);
export const chosenInterlocutorAtom = atom<string>('0x26C494d6526Df0c43c01480Ee07a870d4Eb0B647');
interface ChatMessage {
id: string;
sender: string;
receiver: string;
content: string;
timestamp: number;
}
export const chatMessagesAtom = atom<ChatMessage[]>([]);
export const newMessageAtom = atom<string>('');
Create a backend service to store and retrieve chat messages in backend/chatService.js
.
backend/chatService.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
mongoose.connect('mongodb://localhost:27017/chatDB', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
console.log('Connected to MongoDB');
});
const messageSchema = new mongoose.Schema({
sender: String,
receiver: String,
content: String,
timestamp: { type: Date, default: Date.now },
});
const Message = mongoose.model('Message', messageSchema);
const app = express();
app.use(bodyParser.json());
app.post('/api/chat/messages', async (req, res) => {
const { sender, receiver, content } = req.body;
const newMessage = new Message({ sender, receiver, content });
try {
const savedMessage = await newMessage.save();
res.status(201).json(savedMessage);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.get('/api/chat/messages', async (req, res) => {
const { sender, receiver } = req.query;
try {
const messages = await Message.find({
$or: [
{ sender, receiver },
{ sender: receiver, receiver: sender },
],
}).sort({ timestamp: 1 });
res.status(200).json(messages);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Ensure ChatMessage
component correctly displays messages from the backend.
components/Market/TradingHub/Chat/ChatMessage.tsx
export interface ChatMessageProps {
author: MessageAuthorType;
message: string;
}
export const ChatMessage = ({ author, message }: ChatMessageProps) => {
return (
<div
className={`flex w-full ${author === 'user' && 'justify-end'} ${
author === 'interlocutor' && 'justify-start'
}`}
>
<div
className={`max-w-[50%] break-normal px-[16px] py-[8px]
mb-[16px] ${
author === 'user' &&
'rounded-bl-2xl rounded-tl-2xl rounded-tr-2xl bg-[#87DAA4]'
} ${
author === 'interlocutor' &&
'rounded-br-2xl rounded-tr-2xl rounded-tl-2xl bg-[#23252E]'
}`}
>
<p
className={`break-words text-xs ${
author === 'user' ? 'text-black' : 'text-tetriary'
}`}
>
{message}
</p>
</div>
</div>
);
};
Integrate the new backend chat service into ChatContainer.tsx
.
components/Market/TradingHub/Chat/ChatContainer.tsx
import React, { useEffect, useRef, useState } from 'react';
import { IoSend } from 'react-icons/io5';
import { ChatMessage, ChatMessageProps } from './ChatMessage';
import { useAccount } from 'wagmi';
import { truncateAddress } from '@/utils/truncateAddress';
import { FaSearch } from 'react-icons/fa';
import { useAtom } from 'jotai';
import { chosenInterlocutorAtom } from '@/store/store';
import { fetchMessages, sendMessage } from '@/services/chatService';
export const ChatContainer = () => {
const [messages, setMessages] = useState<ChatMessageProps[]>([]);
const [inputVal, setInputVal] = useState<string>('');
const [chosenInterlocutor, setChosenInterlocutor] = useAtom(chosenInterlocutorAtom);
const messagesEndRef = useRef<HTMLDivElement>(null);
const { address } = useAccount();
useEffect(() => {
const loadMessages = async () => {
if (chosenInterlocutor) {
const fetchedMessages = await fetchMessages(chosenInterlocutor);
setMessages(fetchedMessages);
}
};
loadMessages();
}, [chosenInterlocutor]);
const handleSendMessage = async () => {
if (inputVal.trim() !== '') {
const message: ChatMessageProps = { author: address, message: inputVal };
const newMessage = await sendMessage({ ...message, interlocutor: chosenInterlocutor });
setMessages((prevMessages) => [...prevMessages, newMessage]);
setInputVal('');
}
};
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
handleSendMessage();
}
};
return (
<div className='w-full h-full flex pt-4' style={{ height: 'calc(100vh - 229px)' }}>
<div className='w-[200px] h-full border-r border-[#444650] border-t'>
<div className='w-full px-2 my-4'>
<div className='w-full bg-[#23252E] h-[32px] rounded-[100px] flex justify-between items-center mb-3'>
<input
placeholder='Search'
type='text'
className='w-[85%] outline-none bg-[#23252E] h-full rounded-[100px] pl-3 text-xs text-tetriary'
/>
<div className='pr-3 text-tetriary text-sm'>
<FaSearch />
</div>
</div>
</div>
<div className='mb-3'>
<p className='text-[10px] text-tetriary px-2'>All messages</p>
</div>
</div>
<div className='flex-grow h-full border-t border-[#444650] flex flex-col justify-between'>
<div className='flex flex-col justify-between h-full'>
<div className='h-[56px] border-b border-[#444650] flex items-center justify-between px-3'>
<div className='flex items-center gap-1.5'>
<div className='h-4 w-4 rounded-full bg-white'></div>
<p className='text-tetriary text-sm'>
{truncateAddress(chosenInterlocutor)}
</p>
</div>
<div>
<p className='text-[10px] text-tetriary'>
PnL with Opponent:{' '}
<span className='font-bold text-[#87DAA4]'>
+400.50 $DOLLARS
</span>
</p>
</div>
</div>
<div className='flex-1 overflow-auto pb-[16px] no-scrollbar'>
<div className=' flex flex-col p-3 first:mt-2'>
{messages.map((message, key) => (
<ChatMessage
author={message.author}
key={key}
message={message.message}
/>
))}
</div>
</div>
<div className='w-full px-3'>
<div className='w-full bg-[#23252E] h-[42px] rounded-[100px] flex justify-between items-center mb-3'>
<input
onChange={(e) => setInputVal(e.target.value)}
type='text'
value={inputVal}
onKeyPress={handleKeyPress}
placeholder='Your message here'
className='w-[85%] outline-none bg-[#23252E] h-full rounded-[100px] pl-3 text-xs text-tetriary'
/>
<button
className='pr-3 text-tetriary'
onClick={handleSendMessage}
>
<IoSend />
</button>
</div>
</div>
</div>
</div>
</div>
);
};
Click here to create a Pull Request with the proposed solution
Files used for this task:
{message} {/* Adjusted from 'value' to 'message' */}
All messages
{truncateAddress(chosenInterlocutor)}
PnL with Opponent:{' '} +400.50 $DOLLARS
Is your feature request related to a problem? Please describe. I want to add feature that will allow users to chat peer to peer whenever they have position pair. graphql doesnt store messages can you create some backend to store it?