Skip to content

Commit

Permalink
Merge pull request #54 from bettersg/46-add-chat-to-homepage
Browse files Browse the repository at this point in the history
add chat to homepage
  • Loading branch information
yevkim authored Nov 11, 2024
2 parents 04afb86 + 0b94a62 commit b50b29d
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 1 deletion.
2 changes: 2 additions & 0 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import MainChat from "@/components/main-chat/main-chat";

export default function Home() {
return (
<main>
<MainChat />
</main>
)
}
47 changes: 47 additions & 0 deletions frontend/src/components/chat-list/chat-list.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.chatList {
display: flex;
flex-direction: column;
gap: 10px;
padding: 16px;
}

.messageContainer {
display: flex;
align-items: flex-start;
}

.botContainer {
justify-content: flex-start;
}

.userContainer {
justify-content: flex-end;
}

.avatar {
margin-right: 8px;
}

.card {
max-width: 80%;
padding: 3px;
border-radius: 16px;
font-size: 0.875rem;
line-height: 1.25rem;
word-wrap: break-word;
}

.cardBody {
padding: 4px 8px;
}

.bot {
background-color: #f1f5f9;
align-self: flex-start;
}

.user {
background-color: #e6f1fe;
color: #006FEE;
align-self: flex-end;
}
35 changes: 35 additions & 0 deletions frontend/src/components/chat-list/chat-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Avatar, Card, CardBody } from "@nextui-org/react";
import classes from "./chat-list.module.css"
import { Message } from "../main-chat/main-chat";

interface ChatListProps {
messages: Message[];
}

export default function ChatList({ messages }: ChatListProps) {
return (
<div className={classes.chatList}>
{messages.map((msg, index) => (
<div key={index} className={`${classes.messageContainer} ${msg.type === "user" ? classes.userContainer : classes.botContainer}`}>
{msg.type === "bot" && (
<Avatar
name="S"
size="sm"
className={classes.avatar}
/>
)}
<Card
className={`${classes.card} ${msg.type === "user" ? classes.user : classes.bot}`}
fullWidth={false}
radius="lg"
shadow="none"
>
<CardBody className={classes.cardBody}>
<p>{msg.text}</p>
</CardBody>
</Card>
</div>
))}
</div>
);
}
60 changes: 60 additions & 0 deletions frontend/src/components/main-chat/main-chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import { useState } from "react";
import ChatList from "@/components/chat-list/chat-list";
import SearchBar from "@/components/search-bar/search-bar";
import { Spacer } from "@nextui-org/react";

export type Message = {
type: "user" | "bot",
text: string
}

export default function MainChat() {
const [messages, setMessages] = useState<Message[]>([
{ type: "bot", text: "Hello! How can I help you today?" }
]);
const [userInput, setUserInput] = useState("");
const [botResponse, setBotResponse] = useState("");
const [isBotResponseGenerating, setIsBotResponseGenerating] = useState<boolean>(false);

const handleUserInput = (input: string) => {
setMessages((prevMessages) => [
...prevMessages,
{ type: "user", text: input }
]);
setUserInput("");
};

const handleBotResponse = (response: string) => {
setMessages((prevMessages) => [
...prevMessages,
{ type: "bot", text: response }
]);
setBotResponse("");
};

//TODO: Change bot response simulation to backend API
const simulateBotResponse = (userMessage: string) => {
setIsBotResponseGenerating(true);
setTimeout(() => {
const botReply = `Bot response to: ${userMessage}`;
handleBotResponse(botReply);
setIsBotResponseGenerating(false);
}, 1000);
};

return (
<>
<ChatList messages={messages} />
<Spacer y={4} />
<SearchBar
userInput={userInput}
setUserInput={setUserInput}
handleUserInput={handleUserInput}
simulateBotResponse={simulateBotResponse}
isBotResponseGenerating={isBotResponseGenerating}
/>
</>
)
}
2 changes: 1 addition & 1 deletion frontend/src/components/main-layout/main-layout.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.contentWrapper {
padding: 2em 5em;
margin: 0 auto;
max-width: 1200px;
max-width: 800px;
box-sizing: border-box;
}
8 changes: 8 additions & 0 deletions frontend/src/components/search-bar/search-bar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.searchBar textarea::placeholder{
font-style: italic;
color: rgba(0, 0, 0, 0.2);
}

.endContent {
margin-top: auto
}
45 changes: 45 additions & 0 deletions frontend/src/components/search-bar/search-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Button, Spinner, Textarea } from "@nextui-org/react";
import { SearchIcon } from './search-icon';
import classes from './search-bar.module.css';

interface SearchBarProps {
userInput: string;
setUserInput: React.Dispatch<React.SetStateAction<string>>;
handleUserInput: (message: string) => void;
simulateBotResponse: (userMessage: string) => void;
isBotResponseGenerating: boolean
}

export default function SearchBar({ userInput, setUserInput, handleUserInput, simulateBotResponse, isBotResponseGenerating }: SearchBarProps) {

const handleSend = () => {
if (userInput.trim()) {
handleUserInput(userInput);
simulateBotResponse(userInput);
}
};

return (
<>
<Textarea
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
className={classes.searchBar}
type="text"
size="md"
radius="lg"
color="primary"
label="How can we help?"
labelPlacement="outside"
placeholder="I am a dialysis patient in need of financial assistance and food support after being retrenched due to Covid-19."
endContent={
isBotResponseGenerating
? <Spinner className={classes.endContent} size="sm" />
: <Button className={classes.endContent} isIconOnly size="sm" radius="full" onClick={handleSend}>
<SearchIcon />
</Button>
}
/>
</>
)
}
27 changes: 27 additions & 0 deletions frontend/src/components/search-bar/search-icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const SearchIcon = (props) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="1em"
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<path
d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z"
stroke="gray"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
<path
d="M22 22L20 20"
stroke="gray"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);

0 comments on commit b50b29d

Please sign in to comment.