-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Feature][Antonio] Added chat messaging to all app users
- Loading branch information
1 parent
645761e
commit 1a73be7
Showing
12 changed files
with
632 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import React, { useState } from "react"; | ||
import { Send } from "lucide-react"; | ||
import { toast } from "sonner"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { Textarea } from "@/components/ui/textarea"; | ||
import useUserState from "@/hooks/useUserState"; | ||
import useChatMessages from "@/hooks/useChatMessages"; | ||
import { ChatMessage } from "@/types/ChatMessage"; | ||
|
||
interface ChatComposeMessageProps { | ||
selectedUser: { | ||
userId: string; | ||
userFirstName: string; | ||
userLastName: string; | ||
userPhotoUrl: string | null; | ||
} | null; | ||
} | ||
|
||
const ChatComposeMessage: React.FC<ChatComposeMessageProps> = ({ | ||
selectedUser, | ||
}) => { | ||
const { userId, userFirstName, userLastName } = useUserState(); | ||
const { addChatMessage } = useChatMessages(); | ||
const [message, setMessage] = useState(""); | ||
|
||
const handleSend = async () => { | ||
if (message.trim() && selectedUser) { | ||
const newChatMessage: ChatMessage = { | ||
message, | ||
isView: true, | ||
sender: { | ||
userId: userId || "", | ||
userFirstName: userFirstName || "", | ||
userLastName: userLastName || "", | ||
}, | ||
recipient: { | ||
userId: selectedUser.userId, | ||
userFirstName: selectedUser.userFirstName, | ||
userLastName: selectedUser.userLastName, | ||
}, | ||
}; | ||
await addChatMessage(newChatMessage); | ||
setMessage(""); | ||
} else if (!selectedUser) { | ||
toast.warning("No user selected."); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="flex items-center space-x-2 p-2 bg-background"> | ||
<Textarea | ||
value={message} | ||
onChange={(e) => setMessage(e.target.value)} | ||
placeholder="Type your message..." | ||
className="flex-grow border-primary" | ||
/> | ||
<Button | ||
onClick={handleSend} | ||
className="h-20 flex items-center justify-center" | ||
> | ||
<Send className="w-5 h-5" /> | ||
</Button> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ChatComposeMessage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import React, { useEffect, useRef } from "react"; | ||
import { Timestamp } from "firebase/firestore"; | ||
import { Trash } from "lucide-react"; | ||
|
||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; | ||
import useUserState from "@/hooks/useUserState"; | ||
import useChatMessages from "@/hooks/useChatMessages"; | ||
import { ChatMessage } from "@/types/ChatMessage"; | ||
|
||
interface ChatMessageHistoryProps { | ||
selectedUser: { | ||
userId: string; | ||
userFirstName: string; | ||
userLastName: string; | ||
userPhotoUrl: string | null; | ||
} | null; | ||
} | ||
|
||
const ChatMessageHistory: React.FC<ChatMessageHistoryProps> = ({ | ||
selectedUser, | ||
}) => { | ||
const { userId, userFirstName, userLastName, userPhotoUrl } = useUserState(); | ||
const { chatMessages, removeFromView } = useChatMessages(); | ||
const messagesEndRef = useRef<HTMLDivElement | null>(null); | ||
|
||
useEffect(() => { | ||
// Scroll to the bottom when messages change | ||
if (messagesEndRef.current) { | ||
messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); | ||
} | ||
}, [chatMessages]); | ||
|
||
if (!selectedUser) { | ||
return ( | ||
<div className="text-center text-muted-foreground py-4"> | ||
Select a user to start chatting. | ||
</div> | ||
); | ||
} | ||
|
||
const filteredMessages = chatMessages | ||
.filter( | ||
(msg: ChatMessage) => | ||
(msg.sender.userId === userId && | ||
msg.recipient.userId === selectedUser.userId) || | ||
(msg.sender.userId === selectedUser.userId && | ||
msg.recipient.userId === userId) | ||
) | ||
.sort((a, b) => { | ||
const dateA = (a.dateCreated as unknown as Timestamp)?.seconds ?? 0; | ||
const dateB = (b.dateCreated as unknown as Timestamp)?.seconds ?? 0; | ||
return dateA - dateB; // Sort in ascending order (oldest on top) | ||
}); | ||
|
||
return ( | ||
<div className="p-4 space-y-4"> | ||
{filteredMessages.length === 0 ? ( | ||
<div className="text-center text-muted-foreground py-4"> | ||
Start a conversation | ||
</div> | ||
) : ( | ||
filteredMessages.map((msg) => { | ||
const isCurrentUser = msg.sender.userId === userId; | ||
return ( | ||
<div | ||
key={msg.id} | ||
className={`flex ${ | ||
isCurrentUser ? "justify-end" : "justify-start" | ||
}`} | ||
> | ||
{!isCurrentUser && ( | ||
<Avatar className="w-10 h-10 border border-muted-foreground mr-2"> | ||
<AvatarImage src={selectedUser?.userPhotoUrl ?? undefined} /> | ||
<AvatarFallback> | ||
{selectedUser?.userFirstName?.[0] ?? "U"} | ||
</AvatarFallback> | ||
</Avatar> | ||
)} | ||
<div | ||
className={`flex flex-col ${ | ||
isCurrentUser ? "items-end" : "items-start" | ||
}`} | ||
> | ||
<div | ||
className={`relative ${ | ||
isCurrentUser | ||
? "border border-primary bg-muted text-primary" | ||
: "border border-muted-foreground bg-muted text-muted-foreground" | ||
} p-2 rounded-lg`} | ||
> | ||
<div className="text-sm md:text-base font-bold"> | ||
{isCurrentUser | ||
? `${userFirstName ?? "Unknown"} ${ | ||
userLastName ?? "User" | ||
}` | ||
: `${selectedUser.userFirstName} ${selectedUser.userLastName}`} | ||
</div> | ||
<div className="text-sm md:text-base"> | ||
{msg.isView ? ( | ||
msg.message | ||
) : ( | ||
<i className="text-xs">Message deleted</i> | ||
)} | ||
</div> | ||
{isCurrentUser && msg.isView && ( | ||
<button | ||
className="absolute -top-0 -left-6" | ||
onClick={() => removeFromView(msg.id || "")} | ||
> | ||
<Trash className="w-4 h-4 text-muted-foreground" /> | ||
</button> | ||
)} | ||
</div> | ||
</div> | ||
{isCurrentUser && ( | ||
<Avatar className="w-10 h-10 border border-primary ml-2"> | ||
<AvatarImage src={userPhotoUrl ?? undefined} /> | ||
<AvatarFallback>{userFirstName?.[0] ?? "U"}</AvatarFallback> | ||
</Avatar> | ||
)} | ||
</div> | ||
); | ||
}) | ||
)} | ||
<div ref={messagesEndRef} /> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ChatMessageHistory; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React, { useState } from "react"; | ||
import ChatUsers from "./chat-users"; | ||
import ChatMessageHistory from "./chat-message-history"; | ||
import ChatComposeMessage from "./chat-compose-message"; | ||
|
||
const ChatUI = () => { | ||
const [selectedUser, setSelectedUser] = useState<{ | ||
userId: string; | ||
userFirstName: string; | ||
userLastName: string; | ||
userPhotoUrl: string | null; | ||
} | null>(null); | ||
|
||
return ( | ||
<div className="flex flex-col"> | ||
<ChatUsers onSelectUser={setSelectedUser} /> | ||
<hr /> | ||
<div className="h-[348px] md:h-[594px] overflow-y-auto "> | ||
<ChatMessageHistory selectedUser={selectedUser} /> | ||
</div> | ||
<div className="fixed left-2 md:left-56 md:ml-2 bottom-0 right-2 "> | ||
<hr /> | ||
<ChatComposeMessage selectedUser={selectedUser} /> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ChatUI; |
Oops, something went wrong.