@@ -169,6 +169,7 @@ export default function Chat({ user }: { user: User }) {
169169 supabase,
170170 userId : user . id ,
171171 showModal,
172+ globalConversationId : GLOBAL_CONVERSATION_ID ,
172173 } ) ;
173174
174175 const { badgesByUserId } = useChatBadges ( {
@@ -197,7 +198,6 @@ export default function Chat({ user }: { user: User }) {
197198 setConversations,
198199 setParticipantMetaByConversationId,
199200 setLastSeenByUserId,
200- setUnreadCountByConversationId,
201201 fetchUnreadCountsForConversations,
202202 markConversationAsRead,
203203 } ) ;
@@ -236,9 +236,10 @@ export default function Chat({ user }: { user: User }) {
236236
237237 const bucketName = process . env . NEXT_PUBLIC_SUPABASE_BUCKET_NAME || "" ;
238238
239- const { sendMessage } = useChatMessageComposer ( {
239+ const { sendMessage, isSendingMessage } = useChatMessageComposer ( {
240240 supabase,
241241 userId : user . id ,
242+ channelRef,
242243 conversationId,
243244 input,
244245 attachments,
@@ -255,6 +256,7 @@ export default function Chat({ user }: { user: User }) {
255256 const { textareaRef, handleInputChange, handleInputKeyDown } =
256257 useChatInputBehavior ( {
257258 input,
259+ isSendingMessage,
258260 conversationId,
259261 attachmentsCount : attachments . length ,
260262 setInput,
@@ -263,10 +265,8 @@ export default function Chat({ user }: { user: User }) {
263265 maxChars : 1000 ,
264266 } ) ;
265267
266- const totalUnreadCount = useMemo (
267- ( ) => Object . values ( unreadCountByConversationId ) . reduce ( ( sum , count ) => sum + count , 0 ) ,
268- [ unreadCountByConversationId ] ,
269- ) ;
268+ const canSendMessage =
269+ ! isSendingMessage && ( input . trim ( ) . length > 0 || attachments . length > 0 ) ;
270270
271271 const globalConversations = conversations . filter ( ( c ) => c . type === "global" ) ;
272272 const privateConversations = conversations
@@ -334,6 +334,15 @@ export default function Chat({ user }: { user: User }) {
334334 . reverse ( ) ;
335335 } , [ messages ] ) ;
336336
337+ // Performance optimization: Memoize the filtered messages to avoid O(N) string manipulation on every keystroke
338+ // when typing a message (which triggers a re-render of Chat.tsx). This significantly improves typing latency
339+ // in conversations with many messages.
340+ const filteredMessages = useMemo ( ( ) => {
341+ if ( ! messageSearch ) return messages ;
342+ const lowerSearch = messageSearch . toLowerCase ( ) ;
343+ return messages . filter ( ( m ) => ( m . text || "" ) . toLowerCase ( ) . includes ( lowerSearch ) ) ;
344+ } , [ messages , messageSearch ] ) ;
345+
337346 return (
338347 < >
339348 < MediaViewerModal
@@ -347,14 +356,7 @@ export default function Chat({ user }: { user: User }) {
347356 < div className = { `w-full md:w-[300px] flex-shrink-0 border-r border-white/5 flex flex-col bg-[#0a0a1a] md:bg-transparent z-20 absolute md:relative h-full transition-transform duration-300 ${ conversationId ? '-translate-x-full md:translate-x-0' : 'translate-x-0' } ` } >
348357 < div className = "p-5 border-b border-white/5" >
349358 < div className = "flex items-center justify-between mb-4" >
350- < div className = "flex items-center gap-2" >
351- < h2 className = "text-lg font-bold text-gray-100 tracking-tight" > Message category</ h2 >
352- { totalUnreadCount > 0 && (
353- < span className = "min-w-[24px] h-6 px-2 rounded-full bg-rose-500/90 text-white text-[11px] font-bold flex items-center justify-center" >
354- { totalUnreadCount > 99 ? "99+" : totalUnreadCount }
355- </ span >
356- ) }
357- </ div >
359+ < h2 className = "text-lg font-bold text-gray-100 tracking-tight" > Message category</ h2 >
358360 < button
359361 onClick = { ( ) => setShowModal ( true ) }
360362 className = "w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center hover:bg-indigo-500/20 transition"
@@ -489,9 +491,7 @@ export default function Chat({ user }: { user: User }) {
489491
490492 < div className = "flex-1 flex flex-col overflow-hidden z-10 min-h-0" >
491493 < Messages
492- messages = { messages . filter ( ( m ) =>
493- ( m . text || "" ) . toLowerCase ( ) . includes ( messageSearch . toLowerCase ( ) )
494- ) }
494+ messages = { filteredMessages }
495495 user = { user }
496496 conversations = { conversations }
497497 bottomRef = { bottomRef }
@@ -559,6 +559,7 @@ export default function Chat({ user }: { user: User }) {
559559 >
560560 < button
561561 onClick = { ( ) => fileInputRef . current ?. click ( ) }
562+ disabled = { isSendingMessage }
562563 className = "w-10 h-10 mb-[2px] rounded-full bg-transparent hover:bg-white/10 flex items-center justify-center transition-all duration-300 flex-shrink-0 group"
563564 title = "Attach file"
564565 >
@@ -589,15 +590,17 @@ export default function Chat({ user }: { user: User }) {
589590 < div className = "mb-[2px] pr-1" >
590591 < button
591592 onClick = { sendMessage }
592- disabled = { ! input . trim ( ) && attachments . length === 0 }
593+ disabled = { ! canSendMessage }
593594 className = { `h-10 px-5 rounded-[20px] font-semibold text-[14px] flex items-center gap-2.5 transition-all duration-300 flex-shrink-0
594- ${ ( input . trim ( ) || attachments . length > 0 )
595+ ${ canSendMessage
595596 ? "bg-gradient-to-r from-indigo-500 to-violet-500 hover:from-indigo-400 hover:to-violet-400 text-white shadow-md shadow-indigo-500/25 active:scale-95"
596597 : "bg-white/5 text-gray-500 cursor-not-allowed" }
597598 ` }
598599 >
599- < span className = "hidden sm:inline" > Send</ span >
600- < FontAwesomeIcon icon = { faPaperPlane } className = { `w-[14px] h-[14px] transition-transform ${ ( input . trim ( ) || attachments . length > 0 ) ? "translate-x-0.5" : "" } ` } />
600+ < span className = "hidden sm:inline" >
601+ { isSendingMessage ? "Sending..." : "Send" }
602+ </ span >
603+ < FontAwesomeIcon icon = { faPaperPlane } className = { `w-[14px] h-[14px] transition-transform ${ canSendMessage ? "translate-x-0.5" : "" } ` } />
601604 </ button >
602605 </ div >
603606 </ div >
0 commit comments