1- import { Download , X } from 'lucide-react' ;
1+ import { Download , X , FileText } from 'lucide-react' ;
2+ import { useState , useEffect } from 'react' ;
23import {
34 Dialog ,
45 DialogContent ,
78} from '../ui/dialog' ;
89import { Button } from '../ui/button' ;
910import type { Attachment } from '@/types' ;
11+ import { isImageFile , isTextFile } from './fileUtils' ;
1012
1113interface AttachmentDialogProps {
1214 isOpen : boolean ;
@@ -17,6 +19,12 @@ interface AttachmentDialogProps {
1719 onDelete : ( attachmentId : number ) => void ;
1820}
1921
22+ interface TextPreview {
23+ content : string ;
24+ filename : string ;
25+ size : number ;
26+ }
27+
2028export const AttachmentDialog = ( {
2129 isOpen,
2230 onClose,
@@ -25,6 +33,108 @@ export const AttachmentDialog = ({
2533 onDownload,
2634 onDelete
2735 } : AttachmentDialogProps ) => {
36+ const [ textPreview , setTextPreview ] = useState < TextPreview | null > ( null ) ;
37+ const [ isLoadingPreview , setIsLoadingPreview ] = useState ( false ) ;
38+ const [ previewError , setPreviewError ] = useState < string | null > ( null ) ;
39+
40+ useEffect ( ( ) => {
41+ if ( attachment && isTextFile ( attachment . original_filename ) && isOpen ) {
42+ loadTextPreview ( attachment . id ) ;
43+ } else {
44+ setTextPreview ( null ) ;
45+ setPreviewError ( null ) ;
46+ }
47+ } , [ attachment , isOpen ] ) ;
48+
49+ const loadTextPreview = async ( attachmentId : number ) => {
50+ setIsLoadingPreview ( true ) ;
51+ setPreviewError ( null ) ;
52+
53+ try {
54+ const response = await fetch ( `/api/attachments/${ attachmentId } /preview` ) ;
55+ if ( ! response . ok ) {
56+ const error = await response . json ( ) ;
57+ throw new Error ( error . error || 'Failed to load preview' ) ;
58+ }
59+
60+ const preview = await response . json ( ) ;
61+ setTextPreview ( preview ) ;
62+ } catch ( error ) {
63+ console . error ( 'Failed to load text preview:' , error ) ;
64+ setPreviewError ( error instanceof Error ? error . message : 'Failed to load preview' ) ;
65+ } finally {
66+ setIsLoadingPreview ( false ) ;
67+ }
68+ } ;
69+
70+ const renderContent = ( ) => {
71+ if ( ! attachment ) return null ;
72+
73+ if ( isImageFile ( attachment . original_filename ) ) {
74+ return (
75+ < div className = "flex items-center justify-center p-4" >
76+ < img
77+ src = { `/api/attachments/${ attachment . id } ` }
78+ alt = { attachment . original_filename }
79+ className = "max-w-full max-h-[70vh] object-contain rounded-lg"
80+ />
81+ </ div >
82+ ) ;
83+ }
84+
85+ if ( isTextFile ( attachment . original_filename ) ) {
86+ if ( isLoadingPreview ) {
87+ return (
88+ < div className = "flex items-center justify-center p-8" >
89+ < div className = "text-muted-foreground" > Loading preview...</ div >
90+ </ div >
91+ ) ;
92+ }
93+
94+ if ( previewError ) {
95+ return (
96+ < div className = "p-4" >
97+ < div className = "flex items-center justify-center p-8 border border-red-500/20 rounded-lg bg-red-500/10" >
98+ < div className = "text-center" >
99+ < FileText className = "h-12 w-12 text-red-400 mx-auto mb-2" />
100+ < div className = "text-red-400 font-medium" > Preview not available</ div >
101+ < div className = "text-red-300 text-sm mt-1" > { previewError } </ div >
102+ </ div >
103+ </ div >
104+ </ div >
105+ ) ;
106+ }
107+
108+ if ( textPreview ) {
109+ return (
110+ < div className = "p-4 w-full" >
111+ < div className = "bg-muted/30 rounded-lg border border-border overflow-hidden w-full" >
112+ < div className = "bg-muted/60 px-3 py-2 border-b border-border" >
113+ < div className = "text-xs text-muted-foreground" >
114+ { textPreview . size } bytes
115+ </ div >
116+ </ div >
117+ < div className = "max-h-[60vh] overflow-auto w-full" >
118+ < pre className = "text-sm p-4 whitespace-pre-wrap font-mono leading-relaxed break-all overflow-x-auto w-full min-w-0" >
119+ { textPreview . content }
120+ </ pre >
121+ </ div >
122+ </ div >
123+ </ div >
124+ ) ;
125+ }
126+ }
127+
128+ return (
129+ < div className = "flex items-center justify-center p-8" >
130+ < div className = "text-center" >
131+ < FileText className = "h-12 w-12 text-muted-foreground mx-auto mb-2" />
132+ < div className = "text-muted-foreground" > Preview not available for this file type</ div >
133+ </ div >
134+ </ div >
135+ ) ;
136+ } ;
137+
28138 return (
29139 < Dialog open = { isOpen } onOpenChange = { onClose } >
30140 < DialogContent className = "max-w-4xl max-h-[90vh] bg-zinc-900/95 border-zinc-800" >
@@ -33,15 +143,9 @@ export const AttachmentDialog = ({
33143 { attachment ?. original_filename }
34144 </ DialogTitle >
35145 </ DialogHeader >
36- < div className = "flex items-center justify-center p-4" >
37- { attachment && (
38- < img
39- src = { `/api/attachments/${ attachment . id } ` }
40- alt = { attachment . original_filename }
41- className = "max-w-full max-h-[70vh] object-contain rounded-lg"
42- />
43- ) }
44- </ div >
146+
147+ { renderContent ( ) }
148+
45149 < div className = "flex justify-center space-x-2 pb-4" >
46150 < Button
47151 variant = "outline"
0 commit comments