@@ -12,6 +12,8 @@ import { v4 as uuidv4 } from "uuid";
1212import { PaperClipIcon } from "@heroicons/react/20/solid" ;
1313import { SocketContext } from "../contexts/SocketContext" ;
1414import { uploadFile as apiUploadFile } from "../services/MindsMeshAPI" ;
15+ import { toast } from "./shadcn/ui/use-toast" ;
16+ import FilePreview from "./chat/FilePreview" ;
1517
1618interface Message {
1719 id : string ;
@@ -35,7 +37,6 @@ const formatTime = (date: Date) => {
3537 return format ( new Date ( date ) , "HH:mm" ) ;
3638} ;
3739
38-
3940const Chat : React . FC < { chatPartner ?: User | null ; onClose ?: ( ) => void } > = ( {
4041 chatPartner,
4142} ) => {
@@ -51,6 +52,8 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
5152
5253 const { socket } = useContext ( SocketContext ) ; // Access socket from context
5354 const senderId = localStorage . getItem ( "userId" ) ;
55+ const MAX_FILE_SIZE = 150 * 1024 * 1024 ;
56+ const [ uploadProgress , setUploadProgress ] = useState ( 0 ) ;
5457
5558 // Track window focus/blur
5659 const [ isActive , setIsActive ] = useState ( document . hasFocus ( ) ) ;
@@ -236,7 +239,19 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
236239
237240 const handleFileSelect = ( e : React . ChangeEvent < HTMLInputElement > ) => {
238241 if ( e . target . files && e . target . files . length > 0 ) {
239- setSelectedFile ( e . target . files [ 0 ] ) ;
242+ const file = e . target . files [ 0 ] ;
243+
244+ // Validate file size
245+ if ( file . size > MAX_FILE_SIZE ) {
246+ toast ( {
247+ title : "File too large" ,
248+ description : `Maximum file size is 150MB. Your file is ${ ( file . size / ( 1024 * 1024 ) ) . toFixed ( 2 ) } MB.` ,
249+ variant : "destructive" ,
250+ } ) ;
251+ return ;
252+ }
253+
254+ setSelectedFile ( file ) ;
240255 }
241256 } ;
242257
@@ -261,8 +276,25 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
261276
262277 try {
263278 setIsUploading ( true ) ;
279+ setUploadProgress ( 0 ) ;
280+
281+ // Simulated progress updates (in real app, you would get this from your upload API)
282+ const progressInterval = setInterval ( ( ) => {
283+ setUploadProgress ( ( prev ) => {
284+ if ( prev >= 90 ) {
285+ clearInterval ( progressInterval ) ;
286+ return prev ;
287+ }
288+ return prev + 10 ;
289+ } ) ;
290+ } , 300 ) ;
291+
264292 const data = await apiUploadFile ( chatPartner . id , formData ) ;
265293
294+ // Complete the progress
295+ clearInterval ( progressInterval ) ;
296+ setUploadProgress ( 100 ) ;
297+
266298 console . log ( "File upload response:" , data ) ;
267299 return {
268300 url : data . fileUrl ,
@@ -274,6 +306,7 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
274306 return null ;
275307 } finally {
276308 setIsUploading ( false ) ;
309+ setTimeout ( ( ) => setUploadProgress ( 0 ) , 1000 ) ; // Reset progress after a delay
277310 }
278311 } ;
279312
@@ -294,9 +327,7 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
294327 id : messageId ,
295328 senderId,
296329 receiverId : chatPartner . id ,
297- text :
298- newMessage . trim ( ) ||
299- ( selectedFile ? `Sent a file: ${ selectedFile . name } ` : "" ) ,
330+ text : newMessage . trim ( ) ,
300331 timestamp : new Date ( ) ,
301332 status : "sending" ,
302333 isRead : false ,
@@ -374,65 +405,69 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
374405 const renderFilePreview = ( message : Message ) => {
375406 if ( ! message . fileUrl ) return null ;
376407
377- console . log (
378- "Rendering file preview:" ,
379- message . fileUrl ,
380- message . fileType ,
381- message . fileName
382- ) ; // Debug log
383-
384- // Ensure we have the file name with extension
385408 const fileName = message . fileName || "file" ;
409+ const fileType = message . fileType || "" ;
410+ const isImage = fileType . startsWith ( "image/" ) ;
411+ const isPdf = fileType === "application/pdf" ;
412+ const isText = fileType . startsWith ( "text/" ) ;
413+ const isDoc = fileType . includes ( "word" ) || fileType . includes ( "document" ) ;
386414
387- const isImage = message . fileType ?. startsWith ( "image/" ) ;
388- const isPdf = message . fileType === "application/pdf" ;
389- const isText = message . fileType ?. startsWith ( "text/" ) ;
390-
391- // Helper function to get appropriate file icon
415+ // Helper function to get appropriate file icon with color
392416 const getFileIcon = ( ) => {
393- if ( isPdf ) return < FileText size = { 16 } className = "mr-2" /> ;
394- if ( isText ) return < FileText size = { 16 } className = "mr-2" /> ;
395- return < File size = { 16 } className = "mr-2" /> ;
417+ if ( isPdf ) return < FileText size = { 16 } className = "mr-2 text-red-500" /> ;
418+ if ( isText ) return < FileText size = { 16 } className = "mr-2 text-green-500" /> ;
419+ if ( isDoc ) return < FileText size = { 16 } className = "mr-2 text-blue-500" /> ;
420+ return < File size = { 16 } className = "mr-2 text-gray-500" /> ;
396421 } ;
397422
398423 return (
399424 < div className = "mt-2 max-w-full" >
400425 { isImage ? (
401- < a
402- href = { message . fileUrl }
403- target = "_blank"
404- rel = "noopener noreferrer"
405- className = "block"
406- download = { fileName }
407- >
408- < img
409- src = { message . fileUrl }
410- alt = { fileName || "Attached image" }
411- className = "max-w-full max-h-48 rounded-lg object-contain"
412- />
413- < span className = "text-xs mt-1 flex items-center" >
414- < Image size = { 12 } className = "mr-1" />
415- { fileName }
416- </ span >
417- </ a >
418- ) : (
419- < div className = "flex flex-col space-y-2" >
426+ < div className = "rounded-lg overflow-hidden border border-gray-200" >
420427 < a
421428 href = { message . fileUrl }
422429 target = "_blank"
423430 rel = "noopener noreferrer"
424- className = "flex items-center p-2 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors "
431+ className = "block "
425432 download = { fileName }
426433 >
427- { getFileIcon ( ) }
428- < span className = "text-sm truncate" > { fileName } </ span >
434+ < div className = "relative pb-[60%] bg-gray-100" >
435+ < img
436+ src = { message . fileUrl }
437+ alt = { fileName || "Attached image" }
438+ className = "absolute inset-0 w-full h-full object-contain"
439+ />
440+ </ div >
441+ < div className = "p-2 bg-white text-xs flex items-center text-gray-600" >
442+ < Image size = { 12 } className = "mr-1" />
443+ < span className = "truncate" > { fileName } </ span >
444+ </ div >
429445 </ a >
446+ </ div >
447+ ) : (
448+ < div className = "flex flex-col space-y-2" >
430449 < a
431450 href = { message . fileUrl }
451+ target = "_blank"
452+ rel = "noopener noreferrer"
453+ className = "flex items-center p-3 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 transition-colors"
432454 download = { fileName }
433- className = "text-xs text-blue-600 hover:underline flex items-center"
434455 >
435- < span > Download { fileName } </ span >
456+ { getFileIcon ( ) }
457+ < div className = "flex-1 min-w-0" >
458+ < span className = "text-sm font-medium text-gray-800 truncate block" >
459+ { fileName }
460+ </ span >
461+ < span className = "text-xs text-gray-500" >
462+ { isPdf
463+ ? "PDF Document"
464+ : isText
465+ ? "Text File"
466+ : isDoc
467+ ? "Word Document"
468+ : "File" }
469+ </ span >
470+ </ div >
436471 </ a >
437472 </ div >
438473 ) }
@@ -575,38 +610,17 @@ const Chat: React.FC<{ chatPartner?: User | null; onClose?: () => void }> = ({
575610 { chatPartner && (
576611 < CardFooter className = "p-4 bg-white border-t flex flex-col" >
577612 { selectedFile && (
578- < div className = "w-full px-3 py-2 mb-3 bg-blue-50 border border-blue-100 rounded-lg" >
579- < div className = "flex items-center" >
580- { /* Add file type icon based on mimetype */ }
581- { selectedFile . type . startsWith ( "image/" ) ? (
582- < Image size = { 16 } className = "text-blue-500 mr-2" />
583- ) : selectedFile . type === "application/pdf" ? (
584- < FileText size = { 16 } className = "text-red-500 mr-2" />
585- ) : (
586- < File size = { 16 } className = "text-gray-500 mr-2" />
587- ) }
613+ < div className = "w-full mb-3" >
614+ < FilePreview file = { selectedFile } onRemove = { handleRemoveFile } />
615+ </ div >
616+ ) }
588617
589- { /* Filename with better truncation */ }
590- < div
591- className = "flex-1 truncate max-w-[calc(100%-60px)]"
592- title = { selectedFile . name }
593- >
594- < span className = "text-sm font-medium text-gray-700" >
595- { selectedFile . name }
596- </ span >
597- < span className = "text-xs text-gray-500 block" >
598- { ( selectedFile . size / 1024 ) . toFixed ( 0 ) } KB
599- </ span >
600- </ div >
601-
602- < button
603- onClick = { handleRemoveFile }
604- className = "ml-2 p-1 rounded-full hover:bg-gray-200 text-gray-500"
605- title = "Remove file"
606- >
607- < X size = { 16 } />
608- </ button >
609- </ div >
618+ { selectedFile && isUploading && uploadProgress > 0 && (
619+ < div className = "w-full h-1 bg-gray-200 rounded-full mt-2 mb-3" >
620+ < div
621+ className = "h-full bg-blue-500 rounded-full transition-all duration-300 ease-out"
622+ style = { { width : `${ uploadProgress } %` } }
623+ />
610624 </ div >
611625 ) }
612626
0 commit comments