11
22import React , { useState , useEffect , useRef } from 'react' ;
33import { useAuth } from '../hooks/useAuth' ;
4- import { ChatMessage } from '../types' ;
5- import { ArrowLeft , Video , Plus , Send , X , Smile , Trash2 , Loader2 , MoreVertical , Image as ImageIcon } from 'lucide-react' ;
4+ import { ArrowLeft , Video , Plus , Send , X , Smile , Trash2 , Loader2 , MoreVertical } from 'lucide-react' ;
65import { format } from 'date-fns' ;
76import { motion , AnimatePresence } from 'framer-motion' ;
87import * as ReactRouterDOM from 'react-router-dom' ;
@@ -57,6 +56,7 @@ const ChatPage: React.FC = () => {
5756 const [ imagePreviews , setImagePreviews ] = useState < string [ ] > ( [ ] ) ;
5857 const [ reactingTo , setReactingTo ] = useState < string | null > ( null ) ;
5958 const [ showStickerPicker , setShowStickerPicker ] = useState ( false ) ;
59+ const chatContainerRef = useRef < HTMLDivElement > ( null ) ;
6060 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
6161 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
6262
@@ -92,6 +92,30 @@ const ChatPage: React.FC = () => {
9292 messagesEndRef . current ?. scrollIntoView ( { behavior : 'smooth' } ) ;
9393 } , [ messages ] ) ;
9494
95+ useEffect ( ( ) => {
96+ const handleClickOutside = ( event : MouseEvent ) => {
97+ if ( ! chatContainerRef . current ?. contains ( event . target as Node ) ) {
98+ setShowStickerPicker ( false ) ;
99+ setReactingTo ( null ) ;
100+ }
101+ } ;
102+
103+ const handleEscape = ( event : KeyboardEvent ) => {
104+ if ( event . key === 'Escape' ) {
105+ setShowStickerPicker ( false ) ;
106+ setReactingTo ( null ) ;
107+ }
108+ } ;
109+
110+ document . addEventListener ( 'mousedown' , handleClickOutside ) ;
111+ document . addEventListener ( 'keydown' , handleEscape ) ;
112+
113+ return ( ) => {
114+ document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
115+ document . removeEventListener ( 'keydown' , handleEscape ) ;
116+ } ;
117+ } , [ ] ) ;
118+
95119 const handleSend = async ( e : React . FormEvent ) => {
96120 e . preventDefault ( ) ;
97121 if ( ! newMessage . trim ( ) && imagePreviews . length === 0 ) return ;
@@ -113,12 +137,18 @@ const ChatPage: React.FC = () => {
113137 const files = e . target . files ;
114138 if ( files ) {
115139 Array . from ( files ) . forEach ( file => {
140+ if ( ! file . type . startsWith ( 'image/' ) ) {
141+ return ;
142+ }
143+
116144 const reader = new FileReader ( ) ;
117145 reader . onloadend = ( ) => {
118146 setImagePreviews ( prev => [ ...prev , reader . result as string ] ) ;
119147 } ;
120148 reader . readAsDataURL ( file ) ;
121149 } ) ;
150+
151+ e . target . value = '' ;
122152 }
123153 } ;
124154
@@ -135,7 +165,7 @@ const ChatPage: React.FC = () => {
135165 } ;
136166
137167 return (
138- < div className = "h-[calc(100vh-140px)] md:h-[calc(100vh-64px)] flex flex-col bg-black text-white relative overflow-hidden rounded-3xl border border-white/5" >
168+ < div ref = { chatContainerRef } className = "h-[calc(100vh-140px)] md:h-[calc(100vh-64px)] flex flex-col bg-black text-white relative overflow-hidden rounded-3xl border border-white/5" >
139169 < header className = "flex items-center justify-between p-4 border-b border-zinc-800 bg-black/80 backdrop-blur-md sticky top-0 z-20" >
140170 < div className = "flex items-center gap-4" >
141171 < button onClick = { ( ) => navigate ( - 1 ) } className = "p-2 hover:bg-zinc-800 rounded-full transition-colors" >
@@ -182,7 +212,7 @@ const ChatPage: React.FC = () => {
182212 < div className = { `flex gap-3 max-w-[85%] ${ isMe ? 'flex-row-reverse' : 'flex-row' } ` } >
183213 < div
184214 className = "w-8 flex-shrink-0 self-end mb-1 cursor-pointer hover:opacity-80 transition-opacity"
185- onClick = { ( ) => ! isMe && ( window . location . href = `/dashboard/profile/${ msg . user_id } ` ) }
215+ onClick = { ( ) => ! isMe && navigate ( `/dashboard/profile/${ msg . user_id } ` ) }
186216 >
187217 { showAvatar && (
188218 < img
@@ -197,7 +227,7 @@ const ChatPage: React.FC = () => {
197227 { ! isMe && showAvatar && (
198228 < span
199229 className = "text-[10px] font-black text-zinc-500 uppercase tracking-tighter ml-1 cursor-pointer hover:text-zinc-400 transition-colors"
200- onClick = { ( ) => window . location . href = `/dashboard/profile/${ msg . user_id } ` }
230+ onClick = { ( ) => navigate ( `/dashboard/profile/${ msg . user_id } ` ) }
201231 >
202232 { msg . profiles . name }
203233 { msg . profiles . is_sponsor && < SponsorBadge size = "sm" count = { msg . profiles . sponsor_count || 0 } /> }
@@ -243,7 +273,7 @@ const ChatPage: React.FC = () => {
243273 { /* Reaction Picker Portal */ }
244274 < AnimatePresence >
245275 { reactingTo === msg . id && (
246- < div className = { isMe ? 'right-0' : 'left-0' } >
276+ < div className = { `absolute bottom-full mb-2 z-30 ${ isMe ? 'right-0' : 'left-0' } ` } >
247277 < ReactionPicker onSelect = { ( emoji ) => {
248278 addReaction ( msg . id , emoji ) ;
249279 setReactingTo ( null ) ;
0 commit comments