Skip to content

Commit 0e30d19

Browse files
committed
feat: impprove chat feature
1 parent bfec3de commit 0e30d19

28 files changed

Lines changed: 1272 additions & 229 deletions

client/bun.lock

Lines changed: 172 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
"react": "^19.1.0",
3333
"react-dom": "^19.1.0",
3434
"react-hook-form": "^7.60.0",
35+
"react-hot-toast": "^2.5.2",
3536
"socket.io-client": "^4.8.1",
37+
"sonner": "^2.0.6",
3638
"tailwind-merge": "^3.3.1",
3739
"tw-animate-css": "^1.3.5",
3840
"vite": "^6.0.0",

client/pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/components/chat/ChatRoom.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
2727

2828
const { connected } = useSocketStatus();
2929
const { user } = useAuthStore();
30-
30+
3131
// Fetch room data
3232
const { data: room, isLoading: roomLoading, error: roomError } = useRoom(roomId);
33-
33+
3434
// Fetch messages with infinite scroll
3535
const {
3636
data: messagesData,
@@ -39,7 +39,7 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
3939
fetchNextPage,
4040
isFetchingNextPage,
4141
} = useInfiniteMessages(roomId);
42-
42+
4343
// Setup real-time message updates
4444
useRealTimeMessages(roomId);
4545

@@ -60,10 +60,10 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
6060

6161
// Handle member removal notifications
6262
useRealTimeMemberRemoval();
63-
63+
6464
// Get messages from store (real-time updates)
6565
const storeMessages = messagesByRoom[roomId] || [];
66-
66+
6767
// Combine server messages with store messages, prioritizing store messages (real-time)
6868
const allMessages = messagesData?.pages.flatMap(page => page.messages) || [];
6969

@@ -86,11 +86,11 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
8686
// Set current room and join via socket
8787
useEffect(() => {
8888
setCurrentRoom(roomId);
89-
89+
9090
if (connected) {
9191
joinRoom(roomId);
9292
}
93-
93+
9494
return () => {
9595
setCurrentRoom(null);
9696
};
@@ -125,8 +125,6 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
125125
);
126126
}
127127

128-
129-
130128
return (
131129
<div className="h-full flex flex-col bg-white">
132130
{/* Room header - Sticky */}
@@ -138,7 +136,7 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
138136
{room.name.charAt(0).toUpperCase()}
139137
</AvatarFallback>
140138
</Avatar>
141-
139+
142140
<div>
143141
<h1 className="font-semibold text-gray-900">{room.name}</h1>
144142
<div className="flex items-center gap-2 text-sm text-gray-500">
@@ -149,7 +147,7 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
149147
</div>
150148
</div>
151149
</div>
152-
150+
153151
<div className="flex items-center gap-2">
154152
{/* Show invite button only for room author */}
155153
{user && room.authorId === user.id && (
@@ -178,7 +176,7 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
178176
/>
179177
</div>
180178
</div>
181-
179+
182180
{/* Connection status - Also sticky */}
183181
{!connected && (
184182
<div className="sticky top-[73px] z-10 bg-yellow-50 border-b border-yellow-200 px-4 py-2">
@@ -188,7 +186,7 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
188186
</div>
189187
</div>
190188
)}
191-
189+
192190
{/* Messages */}
193191
<MessageList
194192
roomId={roomId}
@@ -197,7 +195,7 @@ export function ChatRoom({ roomId }: ChatRoomProps) {
197195
hasNextPage={hasNextPage}
198196
onLoadMore={() => fetchNextPage()}
199197
/>
200-
198+
201199
{/* Message input */}
202200
<MessageInput
203201
roomId={roomId}

client/src/components/chat/MessageInput.tsx

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useRef, useEffect } from 'react';
1+
import React, { useState, useRef, useEffect, useCallback } from 'react';
22
import { Send } from 'lucide-react';
33
import { Button } from '@/components/ui/button';
44
import { useChatStore } from '@/stores/chatStore';
@@ -42,44 +42,44 @@ export function MessageInput({ roomId, disabled = false, placeholder = "Type a m
4242

4343
const handleSubmit = (e: React.FormEvent) => {
4444
e.preventDefault();
45-
45+
4646
if (!message.trim() || disabled) return;
47-
47+
4848
// Send message
4949
sendMessage(roomId, message.trim());
50-
50+
5151
// Clear input
5252
setMessage('');
53-
53+
5454
// Stop typing indicator
5555
if (isTyping) {
5656
setTyping(roomId, false);
5757
setIsTyping(false);
5858
}
59-
59+
6060
// Reset textarea height
6161
if (textareaRef.current) {
6262
textareaRef.current.style.height = 'auto';
6363
}
6464
};
6565

66-
const handleKeyDown = (e: React.KeyboardEvent) => {
67-
if (e.key === 'Enter' && !e.shiftKey) {
66+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
67+
if (e.key === 'Enter') {
6868
e.preventDefault();
6969
handleSubmit(e);
7070
}
71-
};
71+
}, [handleSubmit]);
7272

7373
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
7474
const value = e.target.value;
7575
setMessage(value);
76-
76+
7777
// Auto-resize textarea
7878
if (textareaRef.current) {
7979
textareaRef.current.style.height = 'auto';
8080
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`;
8181
}
82-
82+
8383
// Handle typing indicator with debounce
8484
const now = Date.now();
8585

@@ -132,7 +132,7 @@ export function MessageInput({ roomId, disabled = false, placeholder = "Type a m
132132

133133
return (
134134
<div className="sticky bottom-0 z-10 border-t bg-white p-4 shadow-lg">
135-
<form onSubmit={handleSubmit} className="flex items-end gap-2">
135+
<div className="flex items-end gap-2">
136136
<div className="flex-1 relative">
137137
<textarea
138138
ref={textareaRef}
@@ -145,24 +145,25 @@ export function MessageInput({ roomId, disabled = false, placeholder = "Type a m
145145
style={{ minHeight: '40px', maxHeight: '120px' }}
146146
rows={1}
147147
/>
148-
148+
149149
{/* Emoji picker */}
150150
<div className="absolute right-2 top-1/2 -translate-y-1/2">
151151
<EmojiPicker onEmojiSelect={handleEmojiSelect} disabled={disabled} />
152152
</div>
153153
</div>
154-
154+
155155
<Button
156-
type="submit"
156+
type="button"
157157
size="sm"
158158
disabled={!message.trim() || disabled}
159159
className="h-10 px-3"
160+
onClick={(e) => handleSubmit(e)}
160161
>
161162
<Send className="w-4 h-4" />
162163
<span className="sr-only">Send message</span>
163164
</Button>
164-
</form>
165-
165+
</div>
166+
166167
{/* Character count (optional) */}
167168
{message.length > 1800 && (
168169
<div className="mt-1 text-xs text-gray-500 text-right">

0 commit comments

Comments
 (0)