@@ -19,8 +19,11 @@ interface UploadHistory {
1919 [ address : string ] : UploadRecord [ ] ;
2020}
2121
22+ type FileType = 'all' | 'images' | 'videos' | 'audio' | 'archives' | 'websites' ;
23+
2224const UploadHistorySection : React . FC < UploadHistoryProps > = ( { address, setShowUploadHistory } ) => {
2325 const [ history , setHistory ] = React . useState < UploadRecord [ ] > ( [ ] ) ;
26+ const [ selectedFilter , setSelectedFilter ] = React . useState < FileType > ( 'all' ) ;
2427
2528 const formatStampId = ( stampId : string ) => {
2629 if ( ! stampId || typeof stampId !== 'string' || stampId . length < 10 ) {
@@ -61,6 +64,118 @@ const UploadHistorySection: React.FC<UploadHistoryProps> = ({ address, setShowUp
6164 return archiveExtensions . some ( ext => filename . toLowerCase ( ) . endsWith ( ext ) ) ;
6265 } ;
6366
67+ // File type detection functions
68+ const getFileType = ( filename ?: string ) : FileType => {
69+ if ( ! filename ) return 'all' ;
70+
71+ const extension = filename . toLowerCase ( ) ;
72+
73+ // Image files
74+ const imageExtensions = [
75+ '.jpg' ,
76+ '.jpeg' ,
77+ '.png' ,
78+ '.gif' ,
79+ '.svg' ,
80+ '.webp' ,
81+ '.bmp' ,
82+ '.ico' ,
83+ '.tiff' ,
84+ '.tif' ,
85+ ] ;
86+ if ( imageExtensions . some ( ext => extension . endsWith ( ext ) ) ) {
87+ return 'images' ;
88+ }
89+
90+ // Video files
91+ const videoExtensions = [
92+ '.mp4' ,
93+ '.avi' ,
94+ '.mov' ,
95+ '.wmv' ,
96+ '.flv' ,
97+ '.webm' ,
98+ '.mkv' ,
99+ '.m4v' ,
100+ '.3gp' ,
101+ '.ogv' ,
102+ ] ;
103+ if ( videoExtensions . some ( ext => extension . endsWith ( ext ) ) ) {
104+ return 'videos' ;
105+ }
106+
107+ // Audio files
108+ const audioExtensions = [ '.mp3' , '.wav' , '.flac' , '.aac' , '.ogg' , '.wma' , '.m4a' , '.opus' ] ;
109+ if ( audioExtensions . some ( ext => extension . endsWith ( ext ) ) ) {
110+ return 'audio' ;
111+ }
112+
113+ // Archive files
114+ const archiveExtensions = [ '.zip' , '.tar' , '.gz' , '.rar' , '.7z' , '.bz2' ] ;
115+ if ( archiveExtensions . some ( ext => extension . endsWith ( ext ) ) ) {
116+ return 'archives' ;
117+ }
118+
119+ // Website files (common web file extensions)
120+ const webExtensions = [ '.html' , '.htm' , '.css' , '.js' , '.json' ] ;
121+ if ( webExtensions . some ( ext => extension . endsWith ( ext ) ) ) {
122+ return 'websites' ;
123+ }
124+
125+ return 'all' ;
126+ } ;
127+
128+ const getFileTypeLabel = ( filename ?: string ) : string => {
129+ const type = getFileType ( filename ) ;
130+ switch ( type ) {
131+ case 'images' :
132+ return 'Image' ;
133+ case 'videos' :
134+ return 'Video' ;
135+ case 'audio' :
136+ return 'Audio' ;
137+ case 'archives' :
138+ return 'Archive' ;
139+ case 'websites' :
140+ return 'Website' ;
141+ default :
142+ return 'File' ;
143+ }
144+ } ;
145+
146+ // Filter history based on selected filter
147+ const filteredHistory = React . useMemo ( ( ) => {
148+ if ( selectedFilter === 'all' ) {
149+ return history ;
150+ }
151+
152+ return history . filter ( record => {
153+ const fileType = getFileType ( record . filename ) ;
154+ return fileType === selectedFilter ;
155+ } ) ;
156+ } , [ history , selectedFilter ] ) ;
157+
158+ // Get filter counts
159+ const getFilterCounts = React . useMemo ( ( ) => {
160+ const counts = {
161+ all : history . length ,
162+ images : 0 ,
163+ videos : 0 ,
164+ audio : 0 ,
165+ archives : 0 ,
166+ websites : 0 ,
167+ } ;
168+
169+ history . forEach ( record => {
170+ const type = getFileType ( record . filename ) ;
171+ if ( type !== 'all' ) {
172+ counts [ type ] ++ ;
173+ }
174+ } ) ;
175+
176+ return counts ;
177+ } , [ history ] ) ;
178+
64179 const getReferenceUrl = ( record : UploadRecord ) => {
65180 // For non-archive files with a filename, include the filename in the URL
66181 if ( record . filename && ! isArchiveFile ( record . filename ) ) {
@@ -71,25 +186,29 @@ const UploadHistorySection: React.FC<UploadHistoryProps> = ({ address, setShowUp
71186 } ;
72187
73188 const downloadCSV = ( ) => {
74- if ( history . length === 0 ) return ;
189+ // Use filtered history for CSV export
190+ const dataToExport = filteredHistory ;
191+ if ( dataToExport . length === 0 ) return ;
75192
76- // CSV headers
193+ // CSV headers - added File Type column
77194 const headers = [
78195 'Reference' ,
79196 'Stamp ID' ,
80197 'Date Created' ,
81198 'Expiry (Days)' ,
82199 'Filename' ,
200+ 'File Type' ,
83201 'Full Link' ,
84202 ] ;
85203
86204 // Convert history data to CSV rows
87- const csvRows = history . map ( record => [
205+ const csvRows = dataToExport . map ( record => [
88206 record . reference ,
89207 record . stampId ,
90208 formatDate ( record . timestamp ) ,
91209 formatExpiryDays ( record . expiryDate ) ,
92210 record . filename || 'Unnamed upload' ,
211+ getFileTypeLabel ( record . filename ) ,
93212 getReferenceUrl ( record ) ,
94213 ] ) ;
95214
@@ -103,9 +222,12 @@ const UploadHistorySection: React.FC<UploadHistoryProps> = ({ address, setShowUp
103222 const link = document . createElement ( 'a' ) ;
104223 const url = URL . createObjectURL ( blob ) ;
105224 link . setAttribute ( 'href' , url ) ;
225+
226+ // Include filter in filename if not 'all'
227+ const filterSuffix = selectedFilter !== 'all' ? `-${ selectedFilter } ` : '' ;
106228 link . setAttribute (
107229 'download' ,
108- `upload-history-${ address ?. slice ( 0 , 8 ) } -${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .csv`
230+ `upload-history-${ address ?. slice ( 0 , 8 ) } ${ filterSuffix } -${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .csv`
109231 ) ;
110232 link . style . visibility = 'hidden' ;
111233 document . body . appendChild ( link ) ;
@@ -134,7 +256,15 @@ const UploadHistorySection: React.FC<UploadHistoryProps> = ({ address, setShowUp
134256 const fields = line . split ( ',' ) . map ( field => field . replace ( / ^ " | " $ / g, '' ) . trim ( ) ) ;
135257
136258 if ( fields . length >= 6 ) {
137- const [ reference , stampId , dateCreated , expiryDays , filename , fullLink ] = fields ;
259+ const [
260+ reference ,
261+ stampId ,
262+ dateCreated ,
263+ expiryDays ,
264+ filename ,
265+ fileTypeOrFullLink ,
266+ fullLink ,
267+ ] = fields ;
138268
139269 // Skip if reference already exists (prevent duplicates)
140270 if ( existingReferences . has ( reference ) ) {
@@ -213,7 +343,7 @@ const UploadHistorySection: React.FC<UploadHistoryProps> = ({ address, setShowUp
213343 < div className = { styles . titleContainer } >
214344 < h2 className = { styles . title } > Upload History</ h2 >
215345 < div className = { styles . buttonGroup } >
216- { history . length > 0 && (
346+ { filteredHistory . length > 0 && (
217347 < button className = { styles . downloadButton } onClick = { downloadCSV } title = "Download CSV" >
218348 < svg
219349 width = "20"
@@ -277,16 +407,72 @@ const UploadHistorySection: React.FC<UploadHistoryProps> = ({ address, setShowUp
277407 </ div >
278408 </ div >
279409
410+ { /* Filter buttons */ }
411+ { history . length > 0 && (
412+ < div className = { styles . filterContainer } >
413+ < div className = { styles . filterButtons } >
414+ < button
415+ className = { `${ styles . filterButton } ${ selectedFilter === 'all' ? styles . activeFilter : '' } ` }
416+ onClick = { ( ) => setSelectedFilter ( 'all' ) }
417+ >
418+ All ({ getFilterCounts . all } )
419+ </ button >
420+ < button
421+ className = { `${ styles . filterButton } ${ selectedFilter === 'images' ? styles . activeFilter : '' } ` }
422+ onClick = { ( ) => setSelectedFilter ( 'images' ) }
423+ disabled = { getFilterCounts . images === 0 }
424+ >
425+ Images ({ getFilterCounts . images } )
426+ </ button >
427+ < button
428+ className = { `${ styles . filterButton } ${ selectedFilter === 'videos' ? styles . activeFilter : '' } ` }
429+ onClick = { ( ) => setSelectedFilter ( 'videos' ) }
430+ disabled = { getFilterCounts . videos === 0 }
431+ >
432+ Videos ({ getFilterCounts . videos } )
433+ </ button >
434+ < button
435+ className = { `${ styles . filterButton } ${ selectedFilter === 'audio' ? styles . activeFilter : '' } ` }
436+ onClick = { ( ) => setSelectedFilter ( 'audio' ) }
437+ disabled = { getFilterCounts . audio === 0 }
438+ >
439+ Audio ({ getFilterCounts . audio } )
440+ </ button >
441+ < button
442+ className = { `${ styles . filterButton } ${ selectedFilter === 'archives' ? styles . activeFilter : '' } ` }
443+ onClick = { ( ) => setSelectedFilter ( 'archives' ) }
444+ disabled = { getFilterCounts . archives === 0 }
445+ >
446+ Archives ({ getFilterCounts . archives } )
447+ </ button >
448+ < button
449+ className = { `${ styles . filterButton } ${ selectedFilter === 'websites' ? styles . activeFilter : '' } ` }
450+ onClick = { ( ) => setSelectedFilter ( 'websites' ) }
451+ disabled = { getFilterCounts . websites === 0 }
452+ >
453+ Websites ({ getFilterCounts . websites } )
454+ </ button >
455+ </ div >
456+ </ div >
457+ ) }
458+
280459 { ! address ? (
281460 < div className = { styles . emptyState } > Connect wallet to check upload history</ div >
282461 ) : history . length === 0 ? (
283462 < div className = { styles . emptyState } > No uploads found for this address</ div >
463+ ) : filteredHistory . length === 0 ? (
464+ < div className = { styles . emptyState } >
465+ No { selectedFilter === 'all' ? 'files' : selectedFilter } found in your upload history
466+ </ div >
284467 ) : (
285468 < div className = { styles . historyList } >
286- { history . map ( ( record , index ) => (
469+ { filteredHistory . map ( ( record , index ) => (
287470 < div key = { index } className = { styles . historyItem } >
288471 < div className = { styles . itemHeader } >
289- < span className = { styles . filename } > { record . filename || 'Unnamed upload' } </ span >
472+ < div className = { styles . filenameContainer } >
473+ < span className = { styles . filename } > { record . filename || 'Unnamed upload' } </ span >
474+ < span className = { styles . fileType } > { getFileTypeLabel ( record . filename ) } </ span >
475+ </ div >
290476 < span className = { styles . date } > { formatDate ( record . timestamp ) } </ span >
291477 </ div >
292478 < div className = { styles . itemDetails } >
0 commit comments