Skip to content

Commit 9f709b8

Browse files
authored
Context refactor (#18)
* refactor to use context * isThinking, cleanup * format
1 parent 07b15f3 commit 9f709b8

12 files changed

+184
-258
lines changed

src/components/ChatBox.tsx

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,16 @@
1-
import { InputMessage } from "@/components/InputMessage";
1+
import { MessageInput } from "@/components/MessageInput";
22
import { MessageList } from "@/components/MessageList";
3-
import { useState } from "react";
43

5-
export const ChatBox = ({
6-
sendMessage,
7-
owner,
8-
messages,
9-
ownerAvatar,
10-
resetTyping,
11-
typing,
12-
}) => {
13-
const [isLoading, setIsLoading] = useState<Boolean>();
14-
const sendMessageLoading = (sender, senderAvatar, message) => {
15-
setIsLoading(true);
16-
sendMessage(sender, senderAvatar, message);
17-
setTimeout(() => {
18-
setIsLoading(false);
19-
}, 400);
20-
};
4+
export const ChatBox = () => {
215
return (
22-
<div className="flex flex-col rounded-md w-full max-h-full bg-white justify-between mb-10">
23-
<div className="rounded-lg w-full overflow-auto">
24-
<MessageList owner={owner} messages={messages} />
25-
</div>
26-
<div className="p-4">
27-
<InputMessage
28-
isLoading={isLoading}
29-
owner={owner}
30-
ownerAvatar={ownerAvatar}
31-
sendMessage={sendMessage}
32-
sendMessageLoading={sendMessageLoading}
33-
typing={typing}
34-
resetTyping={resetTyping}
35-
/>
6+
<div className="flex justify-center h-full">
7+
<div className="flex flex-col rounded-md w-full max-h-full bg-white justify-between mb-10">
8+
<div className="rounded-lg w-full overflow-auto">
9+
<MessageList />
10+
</div>
11+
<div className="p-4">
12+
<MessageInput />
13+
</div>
3614
</div>
3715
</div>
3816
);

src/components/DebugList.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Button } from "./Button";
2+
3+
export const DebugPanel = () => {
4+
return (
5+
<div>
6+
<Button></Button>
7+
</div>
8+
);
9+
};

src/components/InputMessage.tsx

Lines changed: 0 additions & 85 deletions
This file was deleted.

src/components/MessageInput.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useChatContext } from "@/contexts/ChatContext";
2+
import { useState } from "react";
3+
4+
export const MessageInput = ({}) => {
5+
const [messageInput, setMessageInput] = useState<string>("");
6+
7+
const { addMessage } = useChatContext();
8+
9+
const handleSendMessage = (e) => {
10+
e.preventDefault();
11+
if (messageInput.length > 0) {
12+
// TODO: await
13+
addMessage({ isBot: false, payload: messageInput });
14+
setMessageInput("");
15+
}
16+
};
17+
18+
const sendButtonIcon = (
19+
<svg
20+
xmlns="http://www.w3.org/2000/svg"
21+
fill="none"
22+
viewBox="0 0 24 24"
23+
strokeWidth={1.5}
24+
stroke="currentColor"
25+
className="h-6 w-6"
26+
>
27+
<path
28+
strokeLinecap="round"
29+
strokeLinejoin="round"
30+
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
31+
/>
32+
</svg>
33+
);
34+
35+
return (
36+
<form onSubmit={handleSendMessage}>
37+
<div className="flex">
38+
<input
39+
type="text"
40+
onChange={(e) => setMessageInput(e.target.value)}
41+
className="form-control mr-4 block w-full rounded-full border border-solid border-gray-300 bg-gray-300 bg-clip-padding px-3 py-1.5 text-base font-normal text-gray-700 transition ease-in-out focus:border-blue-600 focus:bg-gray-300 focus:text-gray-700 focus:outline-none"
42+
placeholder="Enter your message..."
43+
tabIndex={0}
44+
value={messageInput}
45+
/>
46+
<button
47+
className="relative float-right box-border h-10 w-10 cursor-pointer select-none rounded-full bg-blue-600 p-2 text-center text-white"
48+
onClick={handleSendMessage}
49+
>
50+
{sendButtonIcon}
51+
</button>
52+
</div>
53+
</form>
54+
);
55+
};

src/components/MessageItem.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
export const MessageItem = ({ owner, sender, senderAvatar, message }) => {
2-
// orient right if I'm sender
3-
const isSender = owner === sender;
1+
import { Message, useChatContext } from "@/contexts/ChatContext";
42

3+
export const MessageItem = ({ message }: { message: Message }) => {
4+
const { isBot, payload } = message;
5+
const { getAvatar } = useChatContext();
6+
7+
const avatar = getAvatar(isBot);
58
return (
6-
<div className={`flex ${isSender ? "flex-row-reverse ml-auto" : ""}`}>
7-
<img src={senderAvatar} alt={sender} className="rounded-full w-10 h-10" />
9+
<div className={`flex ${isBot ? "" : "flex-row-reverse ml-auto"}`}>
10+
<img
11+
src={avatar}
12+
alt={isBot ? "Bot avatar" : "My avatar"}
13+
className="rounded-full w-10 h-10"
14+
/>
815
<div
916
className={`overflow-hidden rounded-md mx-2 p-2 text-sm ${
10-
isSender ? "text-white bg-blue-600" : "text-black bg-gray-200"
17+
isBot ? "text-black bg-gray-200" : "text-white bg-blue-600"
1118
}`}
12-
dangerouslySetInnerHTML={{ __html: message }}
19+
dangerouslySetInnerHTML={{ __html: payload }}
1320
></div>
1421
</div>
1522
);

src/components/MessageList.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import { MessageItem } from "@/components/MessageItem";
2+
import { useChatContext } from "@/contexts/ChatContext";
23

3-
export const MessageList = ({ messages, owner }) => {
4+
export const MessageList = () => {
5+
const { messages, isBotThinking } = useChatContext();
46
return (
5-
<div className="flex flex-col-reverse gap-4 overflow-y-auto w-full max-h-full rounded-md p-4">
6-
{messages
7-
.slice(0)
8-
.reverse()
9-
.map((messageItem) => (
10-
<MessageItem
11-
key={messageItem.id}
12-
owner={owner}
13-
sender={messageItem.sender}
14-
senderAvatar={messageItem.senderAvatar}
15-
message={messageItem.message}
16-
/>
17-
))}
7+
<div className="flex flex-col gap-4 overflow-y-auto w-full max-h-full rounded-md p-4">
8+
{messages.map((message, i) => (
9+
<MessageItem key={`m${i}`} message={message} />
10+
))}
11+
{isBotThinking && <div>Bot is thinking...</div>}
1812
</div>
1913
);
2014
};

src/components/Title.tsx

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/components/TypingIndicator.tsx

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/contexts/ChatContext.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
useState,
3+
ReactNode,
4+
createContext,
5+
useContext,
6+
useEffect,
7+
} from "react";
8+
import useWebSocket from "react-use-websocket";
9+
10+
export type Message = {
11+
isBot: boolean;
12+
payload: string;
13+
};
14+
15+
type UserMessage = Omit<Message, "avatar">;
16+
17+
export type ChatContextType = {
18+
messages: Message[];
19+
addMessage: (msg: UserMessage) => void;
20+
getAvatar: (isBot: boolean) => string;
21+
isBotThinking: boolean;
22+
};
23+
24+
const userAvatar = "https://i.pravatar.cc/150?img=56";
25+
const botAvatar = "https://i.pravatar.cc/150?img=32";
26+
const getAvatar = (isBot: boolean) => (isBot ? botAvatar : userAvatar);
27+
28+
const initialContext = {
29+
messages: [
30+
{
31+
isBot: true,
32+
payload: "Hello 👋,How can I help you?",
33+
},
34+
],
35+
addMessage: (msg: UserMessage) => {},
36+
getAvatar,
37+
isBotThinking: false,
38+
};
39+
40+
const ChatContext = createContext<ChatContextType>(initialContext);
41+
42+
export const ChatContextProvider = ({ children }: { children: ReactNode }) => {
43+
const [messages, setMessages] = useState<Message[]>(initialContext.messages);
44+
const [isBotThinking, setIsBotThinking] = useState<boolean>(
45+
initialContext.isBotThinking
46+
);
47+
const {
48+
sendMessage: wsSendMessage,
49+
lastMessage,
50+
readyState,
51+
} = useWebSocket("ws://localhost:9999");
52+
53+
useEffect(() => {
54+
if (!lastMessage) return;
55+
const msg = {
56+
payload: lastMessage.data,
57+
isBot: true,
58+
};
59+
setIsBotThinking(false);
60+
setMessages((messages) => [...messages, msg]);
61+
}, [lastMessage]);
62+
63+
const addMessage = (msg: UserMessage) => {
64+
setIsBotThinking(true);
65+
wsSendMessage(msg.payload);
66+
setMessages([...messages, msg]);
67+
};
68+
return (
69+
<ChatContext.Provider
70+
value={{
71+
messages,
72+
addMessage,
73+
getAvatar,
74+
isBotThinking,
75+
}}
76+
>
77+
{children}
78+
</ChatContext.Provider>
79+
);
80+
};
81+
82+
export const useChatContext = () => useContext(ChatContext);

0 commit comments

Comments
 (0)